Django后端基础
1:web系统的基础逻辑---道
web系统本质
提供服务
提供接口或界面供调用
底层逻辑
请求与响应
协议支撑
信息交换的标准
2:web开发的技术支持---术
前端
后端
3:Django开发环境搭建
以Django作为开发框架作为大家的起点,基于大家拥有良好的python基础
开发环境搭建
1.虚拟环境+库 安装django
python -m venv myvenv
myvenv\Scripts\activate.bat
pip install Django
2.start project 项目创建
python -m django startproject djangosite[项目名称]
3.start app 应用程序创建
cd djangosite (外面的djangosite)
python manage.py startapp demo
4.注册app:目的是被Django发现,以便可以扫描到数据模型和模板
4:Djanog基本架构
Modle:和数据库交互,通过Model可以不用直接操作数据库,提高了代码编写效率,重点
View:处理用户的请求,和返回数据响应,可以直达用户,重点
Template:django独特的网页渲染方式,将内容配合模板渲染成网页展现给用户,需要配合前端基本
知识使用(html css js)如果前后端分离则用不到此模块 了解即可
5:Django 基础详解
python解释器安装的库和全局是没有影响的 不同项目依赖的版本不一致,为了杜绝版本依赖的问题,每个项目的依赖都是独立的 python里默认安装的库都是全局的,不同版本会造成干扰,手动创建虚拟环境,让不同项目都有自己的独立空间,环境也独立---手动创建虚拟环境 python -m venv djenv: 创建一个解释器环境还包含所依赖的库文件,最基础的库文件,可以自己扩展安装其他的库 python3自带 venv 模块 djenv:虚拟环境的名称 为了让项目所依赖的环境隔离,因为不同项目可能依赖不同环境和不同的库文件版本,所以创建虚拟环境,隔离副本,不影响其他项目 djenv\Scripts\activate.bat:激活虚拟环境 激活后命令行的右侧出现(djenv),表示已经进入虚拟环境了 进入虚拟环境后就可以在虚拟环境安装Django等各种库:pip install Django start project 项目创建:创建Django项目 python -m django startproject <projectname> 创建项目的目录 django-admin startproject <projectname> 创建项目的目录,这个需要处于Django 命令空间才能使用 现在就可以使用pycharm 打开创建的Django项目 projectname:项目工程目录 projectname:和项目工程目录名字一样,这是工程配置文件所在子目录,项目的名字,这个名称不能修改,而项目工程目录名称可以修改的 djangosite:项目工程目录 djangosite:项目的名字 settings.py:配置,主配置文件 asgi.py:服务器配置文件,异步配置文件 网关接口 wsgi.py:服务器配置文件,同步,python 2只有这个没有aasgi.py 网关接口 urls.py:总路由文件,主路由文件 manage.py:项目管理脚本 demo:创建的应用程序 djenv:虚拟的解释器环境 脚本存放的目录,脚本执行调用的解释器环境 分清楚 cd djangosite (外面的djangosite) django-admin startapp demo 创建一个应用程序,写的项目的逻辑都在这个demo文件里,应用程序的名字就叫demo Django一个项目里可以有多个应用程序的,多个应用程序都是可插拔,应用程序写好了可以直接移植到其他的Django项目里 创建了demo文件夹: admin.py:后台管理 apps.py: models.py:数据模型 tests.py:默认编写测试用例的 views.py:视图 这些文件都可以改,Django目前提供的最简单的参考,简单架构 创建了demo web应用程序之后,把web应用程序注册到 djangosite 项目的 sitting 配置文件里,里面就是配置多个应用程序的 配置到 INSTALLED_APPS 这一项,不配置后面可能遇到操作模板找不到各种问题,操作数据也可能出现问题 这样项目才知道这个应用程序,把应用程序下面的逻辑使用过来 除了注册自定义的应用程序 demo,还有django自带的应用程序,帮助开发web应用的 Django项目的启动: 1:djenv\Scripts\activate.bat 进入虚拟环境 2:cd djangosite 进入项目文件,有一个manager.py文件 3:python manage.py runserver 启动整个服务 运行后本地运行一个服务,默认监听 8000 端口 http://127.0.0.1:8000/ 输入这个地址可以直接访问,默认界面 Django总结: 路由定义的时候 习惯末尾 加 / 如果定义了 /: 那么浏览器访问的时候可以不带,浏览器自动补全 请求工具需要自己带斜杠,因为工具不会补全 如果没有定义 /: 那么浏览器访问时候不带 / 工具请求也不带 /
6:Django视图
视图定义:
处理 http 请求的部分,返回 http 响应
Django如何定义视图: # demo/views.py # 定义视图--用户能看到的内容 def index(request): return HttpResponse('冲击年薪40W')
如何访问该视图?
通过路由系统
7:Django的路由系统 djangosite/urls.py
什么是路由? 请求的向导,如用户访问 http://host/path/foo/bar 被系统转到 foo 视图函数,处理后返回结果 路由原理 通过解析传递过来的url,来分配具体执行的视图函数。可以理解为视图的调度器
Django的路由定义: # djangosite/urls.py from login import views urlpatterns = [ path('admin/', admin.site.urls), path('index/',views.index), ]
Django路由转发:
当系统比较复杂时,一层路由会显得很臃肿,且后期不好维护,所以我们可以把路由层层分解,这个叫做路由的转发
蓝色的是总路由【网站主入口服务器】,统一处理用户的请求,后面五颜六色的是分路由,分别处理总路由发过来的请求。
这 个就好比之前的小店老板1个人处理业务就够了,但是现在公司做大了,老板接到活以后要分配给下面 的员工来处理
路由的注意点: 末尾/的问题 一般我们在定义路由的时候习惯末尾加/(斜杠) 如果定义了斜杠 那么浏览器访问的时候可以不带(会自动为你补全) 但是请求工具,比如代码request必须带(不会自动补全) 如果末尾没有定义/(斜杠) 那么浏览器访问的时候不带/(不会自动帮你删/) 同样请求工具,比如代码request也不能带/(不会自动补全也不会自动帮你删/)
8:路由与视图组合练习
1:创建应用程序 ---sgin python -m django startapp sgin # 或者django-admin startapp demo
2:定义URL # djangosite/urls,py from sgin import views urlpatterns = [ path('admin/', admin.site.urls), path('events/',views.events) # 列出发布会 ]
3:发布会管理页面视图 # sgin/views.py from django.http import HttpResponse def events(request): return HttpResponse('测开课程发布会')
4:启动python开发服务器 python manage.py runserver 9090 # 最后一个表示端口号,随意,只要不和当前系统程序冲突即可
5:访问http://127.0.0.1:9090/events/ 会 看到返回:测开课程发布会
6:升级视图-增加发布会数量 def events(request): event_list=['测开课程发布会', '自动化发布会', '性能发布会', '安全发布会', '全栈发布会', 'ISTQB', 'TM项目管理', 'PMP考证'] return HttpResponse('|'.join(event_list))
7:视图再升级-加入html标签 def events(request): event_list=['测开课程发布会', '自动化发布会', '性能发布会', '安全发布会', '全栈发布会', 'ISTQB', 'TM项目管理', 'PMP考证'] # 套入标签<li></li>中 res=''.join([f'<li>{event}</li>' for event in event_list]) return HttpResponse(res)
如上:这时候发现在代码里面修改 html 很麻烦,且不好维护,这个时候我们需要将 html 的内容外包给模板系统 来处理,他负责对外展示的样式【Django模板】
9:Django模板--初识
模板的作用? 发现页面太简陋了? 来看下演示的登录页面--HTML长什么样?---查看网页源码 虽然不是太复杂,但是相对我们这种极度简陋的HTML已经是很复杂了 要如何替换这段呢? 直接把HTML写在文本中呢??? 看下啊 代码太丑陋了,显然不是年薪40W该有的风格~ 正确的做法应该是用HMTL文件来保存待显示的内容,然后我们代码直接返回HTML文件即可 Django早就帮你想到了这点 我们只需利用 Django的模板机制就 可以实现
Django模板:用法-- template【文件夹】 1:首先在应用程序Login的目录下新建一个 templates 的目录 目录名必须相同
2:然后新建HTML文件,里面塞入如下内容 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>发布会</title> </head> <body> <h1>测试开发发布会</h1> </body> </html> 3:模板实践---修改发布会视图返回,改为返回html文件 return render(request,'events.html')
访问 视图函数所映射的接口, 这时候 视图内容(发布会信息)填充到页面中
3:第一个模板技术-变量 作用:将视图函数的变量返回到模板中,django将其渲染 用法:视图返回变量:{'模板中用到的变量名':变量} return render(request,'events.html',{'events':res}) 模板使用变量:{{变量名}} <body> {{ events }} # 标签中加入 </body>
这样编写后刷新页面:
发现变量自带的标签没有被渲染,对的,默认情况下 django 返回的 变量 中若带html标签是不会被渲染 的,当然如果你本就不想渲染 html 标签,或者变量内容没有Html标签那么可以忽略,但是如果想的 话,继续往下看
4:第二个模板技术-for循环控制器 作用:将列表类型的变量挨个展现出来 用法:视图返回变量列表: 修改下原视图函数的返回,之前是字符串,现在改为列表并且去掉标签 # 套入标签<li></li>中 res=[event for event in event_list] return render(request,'events.html',{'events':res},) 模板使用for <ul> {% for event in events %} <li>{{ event }}</li> {% endfor %} </ul> 语法和 python几乎一样!不要忘记 For 循环结束要加 {% endfor %} ,因为Html里是不认你的缩进的。
现在刷新页面,没有标签且正常了
5:第三个模板技术-if控制器 作用:条件控制,可以根据条件选择渲染哪些元素 用法:视图返回变量同上
模板使用变量: {% if 'ISTQB' in events %} <h3>8个课程发布会</h3> {% endif %} 注意:在标签内使用变量不用再加{{}}了,否则会报错
再换一个条件 {% if events.length == 9 %} <h3>9个课程发布会</h3> {% endif %}
发现没有生效,原因是在模板中.length这种列表自带的属性失效了,需要使用模板自带的过滤器来实现
6:模板技术4-过滤器的用法(选修) 作用:转换变量和标签参数的值 写法:{{变量|过滤器}}
上面的案例可以改成
{% if events|length == 9 %}
9个课程发布会
{% endif %}
刷新页面发现可以了,内置的模拟器参考:https://docs.djangoproject.com/zh-hans/3.1/ref/templates/builtins/#ref-templates-builtins-tags
Tips:当哪天你发现内置的模拟器也不能满足需求了,你可以自定义过滤器,不过如果你不专注前端开发
可能很少用到了,参考:https://docs.djangoproject.com/zh-hans/3.1/howto/custom-template-tags/#writing-custom-template-filters
10:页面的美化-更新模板加入静态文件
现在的页面,开发练习练习还可以,但是作为产品展示就显得简陋了,因此需要升级界面。
但是从头写一套页面会花费我们大量的精力,而且工作中,这部分属于前端的战场,所以我们只需要专注后端即可
现在我们从网上下载了现成的漂亮模板,并且稍作一些改动去掉多余的元素和引入文件,做些定制化修改
temp_v1.html <!DOCTYPE html> <html lang="cn"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta content="haiwen" name="author" /> <!-- Bootstrap Styles--> <link href="assets/css/bootstrap.css " rel="stylesheet" /> <!-- FontAwesome Styles--> <link href="assets/css/font-awesome.css" rel="stylesheet" /> <!-- Custom Styles--> <link href="assets/css/custom-styles.css" rel="stylesheet" /> <title></title> </head> <body> <div id="wrapper"> <nav class="navbar navbar-default top-navbar" role="navigation"> <div class="navbar-header"> <a class="navbar-brand"><i class="icon fa fa-plane"></i> 发布会签到系 统</a> <div id="sideNav" > <i class="fa fa-bars icon"></i> </div> </div> <ul class="nav navbar-top-links navbar-right"> <li class="dropdown"> <a class="dropdown-toggle" data-toggle="dropdown" href="#" ariaexpanded="false"> <i class="fa fa-user fa-fw"></i> <i class="fa fa-caretdown"></i> </a> <ul class="dropdown-menu dropdown-user"> <li><a href="#"><i class="fa fa-user fa-fw"></i> User Profile</a> </li> <li><a href="#"><i class="fa fa-gear fa-fw"></i> Settings</a> </li> <li class="divider"></li> <li><a href="#"><i class="fa fa-sign-out fa-fw"></i> Logout</a> </li> </ul> <!-- /.dropdown-user --> </li> </ul> </nav> <!--/. NAV TOP --> 松勤songqin <nav class="navbar-default navbar-side" role="navigation"> <div class="sidebar-collapse"> <ul class="nav" id="main-menu"> <li> <a class="active-menu" href="/sgin/events"><i class="fa fadashboard"></i> 发布会</a> </li> <li> <a href="/sgin/guests"><i class="fa fa-desktop"></i> 嘉宾</a> </li> </ul> </div> </nav> <!-- /. NAV SIDE --> <div id="page-wrapper"> <div class="header"> <div class="page-header"> </div> </div> <div id="page-inner" class="panel-body"> <footer><p>Author:haiwen. <a href="https://ke.qq.com/course/3135766" target="_blank">测试开发</a></p> </footer> </div> <!-- /. PAGE INNER --> </div> <!-- /. PAGE WRAPPER --> </div> <!-- jQuery Js --> <script src="assets/js/jquery-1.10.2.js"></script> <!-- Bootstrap Js --> <script src="assets/js/bootstrap.min.js"></script> <!-- Custom Js --> <script src="assets/js/custom-scripts.js"></script> <script> $(document).ready( $('#main-menu>li>a').each(function (){ $(this).attr('class',''); //先取消选中 let current_href = window.location.pathname if(current_href === $(this).attr('href')){ $(this).attr('class','active-menu'); } }), ) </script> </body> </html>
如上:直接打开是没有样式的,原因是我们还没有引入静态文件-css和js
加入静态文件:
将提供的静态文件按照如上目录结构存放。
由于我们要使用django的模板系统来渲染页面,所以引入静态文件需要遵循django的规则
{% load static %} # 开头加上该标签 <!DOCTYPE html> <html lang="cn">
同时修改外部文件的引入格式
原来的引入方式 <link href="assets/css/bootstrap.css " rel="stylesheet" /> #css <script src="assets/js/jquery-1.10.2.js"></script> #js
修改后
<link href="{% static 'assets/css/bootstrap.css' %} " rel="stylesheet" /> #css <script src="{% static 'assets/js/jquery-1.10.2.js' %}"></script> #js
所以,修改模板文件内容:temp_v2.html {% load static %} <!DOCTYPE html> <html lang="cn"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta content="haiwen" name="author" /> <!-- Bootstrap Styles--> <link href="{% static 'assets/css/bootstrap.css' %} " rel="stylesheet" /> <!-- FontAwesome Styles--> 松勤songqin <link href="{% static 'assets/css/font-awesome.css' %}" rel="stylesheet" /> <!-- Custom Styles--> <link href="{% static 'assets/css/custom-styles.css' %}" rel="stylesheet" /> <title>{% block title%}{% endblock %}</title> </head> <body> <div id="wrapper"> <nav class="navbar navbar-default top-navbar" role="navigation"> <div class="navbar-header"> <a class="navbar-brand"><i class="icon fa fa-plane"></i> 发布会签到系 统</a> <div id="sideNav" > <i class="fa fa-bars icon"></i> </div> </div> <ul class="nav navbar-top-links navbar-right"> <li class="dropdown"> <a class="dropdown-toggle" data-toggle="dropdown" href="#" ariaexpanded="false"> <i class="fa fa-user fa-fw"></i> <i class="fa fa-caretdown"></i> </a> <ul class="dropdown-menu dropdown-user"> <li><a href="#"><i class="fa fa-user fa-fw"></i> User Profile</a> </li> <li><a href="#"><i class="fa fa-gear fa-fw"></i> Settings</a> </li> <li class="divider"></li> <li><a href="#"><i class="fa fa-sign-out fa-fw"></i> Logout</a> </li> </ul> <!-- /.dropdown-user --> </li> </ul> </nav> <!--/. NAV TOP --> <nav class="navbar-default navbar-side" role="navigation"> <div class="sidebar-collapse"> <ul class="nav" id="main-menu"> <li> <a class="active-menu" href="/sgin/events"><i class="fa fadashboard"></i> 发布会</a> </li> <li> <a href="/sgin/guests"><i class="fa fa-desktop"></i> 嘉宾</a> </li> </ul> </div> </nav><!-- /. NAV SIDE --> <div id="page-wrapper"> <div class="header"> <div class="page-header"> {% block maintitle %}{% endblock %} <small></small> </div> </div> <div id="page-inner" class="panel-body"> {% block content %} {% endblock %} <footer><p>Author:haiwen. <a href="https://ke.qq.com/course/3135766" target="_blank">测试开发课程</a></p> </footer> </div> <!-- /. PAGE INNER --> </div> <!-- /. PAGE WRAPPER --> </div> <!-- jQuery Js --> <script src="{% static 'assets/js/jquery-1.10.2.js' %}"></script> <!-- Bootstrap Js --> <script src="{% static 'assets/js/bootstrap.min.js' %}"></script> <!-- Custom Js --> <script src="{% static 'assets/js/custom-scripts.js' %}"></script> <script> $(document).ready( $('#main-menu>li>a').each(function (){ $(this).attr('class',''); //先取消选中 let current_href = window.location.pathname if(current_href === $(this).attr('href')){ $(this).attr('class','active-menu'); } }), ) </script> </body> </html>
拷贝到events.html中,然后重启服务,再刷新页面
填充模板页面: 将发布会信息显示到页面上 {% for event in events %} <li>{{ event }}</li> {% endfor %} 发现不好看,再选择性的加入一些样式 <ul class="list-group"> {% for event in events %} <li class="list-group-item text-center">{{ event }}</li> {% endfor %} </ul> Tips: class="list-group" class="list-group-item text-center" 表示使用该css文件时,class对应 的样式,bootstrap为我们定义了现成的样式,只需要引入类即可,不用从头写css了 这样就完美呈现
11:继续页面的开发
u展示发布会具体信息:名称,时间,地点 等
u从发布会管理页面链接到详情页
u增加返回
此时发现一个问题,两个页面的基本样式都差不多---测边栏和页首菜单,只是中间区域不同
如果只有两个页面还好,多个页面就会出现难以维护的情况,因为可能你改动一个样式就要改N个文件,非常不方便!
所以以我们丰富的编程思路该如何解决问题?
对了,定义一个相对不变的公共模板,其他页面继承这个模板就可以了,模板留出接口,继承的页面只需要改动这个接口就可以。
模板的继承:
我们改动的最后一版html文件中有大量:{% block title%}{% endblock %}
类似这种{% block 标签%}{% endblock %}格式的标签
这个就是模板提供的接口,预留了空间让继承的子页面用于改动的
我们要做的就是继承这个模板页面,改动需要改动的即可
定义基础模板base.html: 将 temp_v2.html 的文件内容拷贝进 base.html 表示根模板的意思 子页面 event.html 改写如下 {% extends "sgin/base.html" %} {% block content %} <ul class="list-group"> {% for event in events %} <li class="list-group-item text-center">{{ event }}</li> {% endfor %} </ul> {% endblock %} 开头的 {% extends "sgin/base.html" %} 表示继承sgin/base.html 页面 {% block content %}{% endblock %} 这之间的内容就填写子页面特有的个性内容。 刷新页面,依旧美丽,但是我们的Html文件简洁了很多 依次类推,我们可以在模板想要改动的地方留出接口,子页面继承的时候可以改动这些接口,这里并不是所有的接口都要子页面填充,你可以只填充你想填充的地方。 原则就是,模板提供的接口,子页面可以填充,没有提供的接口,自然就填充不了哦 依次类推,直接继承父模板,定义子页面
event_detail.html {% extends "base.html" %} {% block title%}发布会详情{% endblock %} {% block content %} <h1>发布会详情</h1> {% endblock %}
定义路由:
path('event_detail',views.event_detail) # 发布会详情
定义视图:
def event_detail(request):
return render(request,'event_detail.html')
这时候方式链接:http://127.0.0.1:9090/event_detail
增加链接: events.html #增加超链接元素 <li class="list-group-item text-center"><a href="/event_detail">{{ event }}</a> </li> {% extends "base.html" %} {% block content %} <ul class="list-group"> {% for event in events %} <li class="list-group-item text-center"><a href="/event_detail">{{ event }}</a></li> {% endfor %} </ul> {% endblock %}
增加返回: event_detail.html <p><a href="/events" class="btn btn-info">返回发布会列表</a></p> {% extends "base.html" %} {% block title%}发布会详情{% endblock %} {% block content %} <h1>发布会详情</h1> <p><a href="/events" class="btn btn-info">返回发布会列表</a></p> {% endblock %}
12:Django深入学习官方链接【路由,视图,模板,内置模板标签和过滤器,Django通用术语】
路由: https://docs.djangoproject.com/zh-hans/3.1/topics/http/urls/
视图: https://docs.djangoproject.com/zh-hans/3.1/topics/http/views/
模板: https://docs.djangoproject.com/zh-hans/3.1/topics/templates/
内置模板标签和过滤器: https://docs.djangoproject.com/zh-hans/3.1/ref/templates/builtins/#ref-templates-builtins-tags
django通用术语: https://docs.djangoproject.com/zh-hans/3.1/glossary/
13:路由的分发
对比下我们目前的路由文件djangosite/urls.py
#第一步导入视图函数
from demo import views
from sgin import views as sgin_view
urlpatterns = [
path('admin/', admin.site.urls),
path('index/',views.index), #第二步,配置路由
#签到系统路由
path('sgin/events',sgin_view.events),
path('sgin/event_detail',sgin_view.event_detail),
]
不同的app路由都统一集中到了一块,如果项目简单还好,项目复杂,url文件就会变得特别臃肿。所以为了后期好维护,我们可以将路由按照以下逻辑逐层分解
多级路由:
我们可以用django的include函数完成这个操作: from django.urls import path,include path('1级路由/',include(视图模块))
在app目录下新建分路由文件urls.py(这个文件名可以自定义,一般叫这个名字):
demo/urls.py from django.urls import path,include from . import views urlpatterns = [ path('index/',views.index), ]
sgin/urls.py from django.urls import path,include from . import views urlpatterns = [ path('events/',views.events), path('event_detail/',views.event_detail), ]
修改主路由,开头导入include: from django.contrib import admin from django.urls import path,include #导入include #第一步导入路由 from demo import urls as demo_view from sgin import urls as sgin_view urlpatterns = [ path('admin/', admin.site.urls), #路由分发 path('demo/',include(demo_view)), # 签到系统路由 path('sgin/',include(sgin_view)), ]
14:数据操作--ORM模型
解决发布会假数据的问题 返回的数据都是一样的 保存数据的途径---数据库? 操作数据库的方法----
1.原生sql
2.ORM模型
ORM的概念:Object Relational Mapping(对象关系映射)
其主要作用是在编程中,把 面向对象的概念跟数据库中表的概念对应起来。 ... 当然这只是从 对象到SQL的映射 ,还有从SQL到对象的映射,也是类似的过程。
有了ORM,我们不需要再写繁琐的sql语句,一切以 python语法为准,通过 ORM提供的API就可以操作数据库
主流的数据库都被Django 官方支持:
PostgreSQL
MariaDB
MySQL
Oracle
SQLite
模型定义:
以 Event(发布会)为例,继承 django 自带的 model 模型类,并定义相应的字段
每个字段类都对应一种数据库字段格式
名称:字符串 最大长度256 非空
开始时间:datetime格式时间
地址:字符串 最大长度256 非空
人数:整数
状态:布尔 默认 true
创建时间:datetime格式时间
常用的数据库字段类型(ORM) 字段类型参考官方文档: https://docs.djangoproject.com/zh-hans/3.1/ref/models/fields/#field-types CharField: 最常用的类型,字符串类型。必须接收一个max_length参数,表示字符串长度不能超过该值。 BooleanField: 布尔值类型。默认值是None DateTimeField: 日期时间类型。Python的datetime.datetime的实例。 IntegerField: 整数类型,范围-2147483648 到 2147483647的值在 Django 支持的所有数据库中都是安全的。
常用字段选项(ORM): 剩余通用字段参考: https://docs.djangoproject.com/zh-hans/3.1/ref/models/fields/#field-options null:如果是 True, Django 将在数据库中存储空值为 NULL。默认为 False。 default:该字段的默认值。可以是一个值或者是个 可调用的对象,不能是一个可更改的对象(模型实例、list、set 等)。 unique:如果设置为 True,这个字段必须在整个表中保持值唯一。默认为False。若为True该字段可以成为一个唯一索引 verbose_name:字段的一个人类可读名称,如果没有给定详细名称,Django 会使用字段的属性名自动创建,并将下划线转换为空格。
模型字段定义代码参考: # 定义发布会模型 --模型名称 Event 对应表名 app_class class Event(models.Model): # 继承django自带的模型基类 # 发布会名称 字符串 最大长度256 非空。 name = models.CharField(max_length=256) # 发布会地点 字符串 最大长度256 非空。 address = models.CharField(max_length=256) # 发布会人数 #默认值100 limits = models.IntegerField(default=100) # 发布会状态 布尔类型,默认为True。 status = models.BooleanField(default=True) # 开始时间 日期时间类型。Python的datetime.datetime的实例。 start_time = models.DateTimeField(null=True)
字段命名约束: Django不允许下面两种字段名: 1:与Python关键字冲突。这会导致语法错误。例如: class Example(models.Model): pass = models.IntegerField() # 'pass'是Python保留字! 2:字段名中不能有两个以上下划线在一起,因为两个下划线是Django的查询语法。例如: class Example(models.Model): foo__bar = models.IntegerField() # 'foo__bar' 有两个下划线在一起! 3:字段名不能以下划线结尾,原因同上。 由于你可以自定义表名、列名,上面的规则可能被绕开,但是请养成良好的习惯,一定不要那么起名。
模型的 __str__方法:可以修改模型自带的str方法,这样在查询的时候,就能看到对应的数据信息了 #定义发布会模型 --模型名称对应表明 app_class class Event(models.Model): #继承django自带的模型基类 #发布会名称 字符串 最大长度256 非空。 name = models.CharField(max_length=256) # 发布会地点 字符串 最大长度256 非空。 address = models.CharField(max_length=256) #发布会人数 #默认值100 limits = models.IntegerField(default=100) # 发布会状态 布尔类型,默认为True。 status = models.BooleanField(default=True) # 开始时间 日期时间类型。Python的datetime.datetime的实例。 start_time = models.DateTimeField(null=True) # 修改模型自带方法 __str__ 调用该对象自动返回对象的name值 def __str__(self): return self.name 对比:修改前 >>> from sgin.models import Event >>> Event.objects.all() <QuerySet [<Event: Event object (1)>]> 对比:修改后: >>> from sgin.models import Event >>> Event.objects.all() <QuerySet [<Event: 测开发布会>]>
激活模型与数据迁移:
激活模型:settings.py中添加应用名称以关联app 配置数据库信息:默认使用的是sqlite,不需要配置,若要改成其他数据库,可以参考以下配置修改:如改成mysql # settings.py DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', # 数据库引擎 'NAME': 'sqtp', # 数据库名称 'USER': 'root', # 用户名 'PASSWORD': 'devops', # 密码 'HOST': '192.168.21.140', # 数据库IP 'PORT': '3306', # 数据库端口 } } 附录:数据库配置 https://docs.djangoproject.com/zh-hans/3.1/intro/tutorial02/
模型迁移3步曲: 1:编辑 models.py 文件,改变模型。
2:运行 python manage.py makemigrations 为模型的改变生成迁移文件,类似以下输出
3:运行 python manage.py migrate 来应用数据库迁移 ,类似以下输出
迁移的内部过程: makemigrations 命令---做语法 预检查,生成操作数据库的命令,并不操作数据库,此时发生的错误都和数据库无关 migrate命令 将前一步的命令落实到数据库,此时发生的错误都和数据库有关
数据的增删改查API: 调用模型 API--- 通过模型的管理器 语法: 模型类.objects.方法
增加:
Model.objects.create(**kws)
修改: 数据对象.字段=值 数据对象.save() #调用save才会保存到数据库 #千万不要用 Model.objects.update(**kws) 会把所有数据记录都更改
删除: 数据对象.delete() # 这里用对象调用删除方法,模型管理器没有,想想为什么?
查询---最丰富的操作: Model.objets.all() ---- 获取所有查询结果,返回 QuerySet 对象 Model.objets.filter(**kw) --- 条件过滤查询,返回 QuerySet 对象,不加参数效果和all()一样 Modle.objets.get() --- 获取单个数据对象 # 没有匹配结果或者匹配结果超过1个都会报错 查询是web中最频繁也是最丰富的操作 ORM执行查询参考:https://docs.djangoproject.com/zh-hans/3.1/topics/db/queries/
测试API--体验Django自带的命令行: python manage.py shell
15:Django自带的后台数据管理
用 API 手动创建的方式还是太麻烦,后续我们可以用 API 编写到接口里面 现在,我们先用Django自带的后台来管理数据 1. 模型注册到admin.py 2. 创建管理员用户 python manage.py createsuperuser 3. 新增数据
16:功能迭代--修改视图
修改event视图: 从数据库读取数据: event_list = Event.objects.all() 修改发布会详情视图 event_detail: 修改视图event_detail def event_detail(request,event_id): event = Event.objects.get(pk=event_id) return render(request,'sgin/event_detail.html',{'event':event})
发布会详情页url可以设计成:event_detail/<int:event_id>
django会把event_detail/2类型的url 解析成 event_detail/<int:event_id>
event_id 可作为event_detail视图函数的参数,注意参数名需要相同
此为django解析foo/path/数字 类型的url的规则 若变化的值为字符串,将int改为str即可
修改视图的url,增加ID:
path('sgin/event_detail/<int:event_id>',views.event_detail),
修改发布会详情页 event_detail.html:更新样式
{% extends 'base.html' %} {% block content %} <div class="panel panel-info"> <div class="panel-heading"> 发布会详情页 </div> <div class="panel-body"> <p>发布会名称:{{ event.name }}</p> <p>发布会时间:{{ event.start_time }}</p> <p>发布会地址:{{ event.address }}</p> <p><a href="/sgin/events" class="btn btn-info">返回列表</a></p> </div> </div> {% endblock %}
修改列表页的URL,events.html
<a href="/sgin/event_detail/{{ event.id }}"> #加上{{ event.id }}
需求2实现嘉宾列表和详情页:
1.创建嘉宾数据模型对象
至少包括手机号,姓名,邮箱,和关联的发布会
2.模仿发布会列表和详情页,创建嘉宾视图路由以及模板页面
/sgin/guests/ #嘉宾列表
/sgin/guest_details/int:id #嘉宾详情页
现在的视图逻辑关系
按照需求,嘉宾需要与发布会进行关联,这个就涉及到了数据库表关联的关系
17:数据库表关联
后端系统开发中, 数据库设计是重中之重。
特别是前后端分离的系统, 后端的职责基本就是 数据管理, 开发的代码几乎都是围绕 数据 操作的。
常用的 数据库表 和 表之间的关系 的设计 。
目前 使用的数据库系统 主要还是 关系型数据库 。
什么是关系型数据库?就是建立在 关系模型基础上的数据库。
大家耳熟能详的 mysql、oracle、 sqlserver、SQLite 都是,而 mongodb、Cassandra不是
而关系型数据库,设计的一个难点就是 各种 表之间的关联关系 。
常见的3种关联关系就是:
一对多 , 一对一 , 多对多
例子
1对多: 1个老师可以上多门课程,对应多门课程 models.ForeignKey # 定义在多方
多对多: 1个学生可以对应多门课程,1门课程也可以对应多个学生 models.ManyToManyField #定义在任意1方,只能定义在1方,不能双方都定义
多对1: 1个男盆友对应1个女盆友.. models.OneToOneField # 定义在多方
嘉宾的与发布会的关系可先定为多对1,参考下图:一个发布会下面可以有多个嘉宾
模型定义代码参考: # 定义发布会关联嘉宾 class Guest(models.Model): # django会自动帮你创建一个id字段作为主键 # 关联发布会:定义在多方【关联发布会的字段】 event = models.ForeignKey(Event,on_delete=models.CASCADE) #CASCADE 如果删除了关联的发布会,该嘉宾也会被删除 # 姓名 字符串 64 唯一 name = models.CharField(max_length=64,unique=True) # 手机号 字符串 11 唯一 phone = models.CharField(max_length=11,unique=True) # 邮箱 邮箱格式 xxx@yyy.zz email = models.EmailField() # 加入时间 --创建数据的时候就自动取当前时间 auto_now_add=True join_time = models.DateTimeField(auto_now_add=True) def __str__(self): return self.name
PS:记住:记运行命令同步到数据库。同时更新url和view
urls.py # 嘉宾列表 path('guests/',views.guests), # 嘉宾详情 # 将guest_id/2类型的url 解析成guest_detail/<int:guest_id> # guest_id 可作为guest_detail视图函数的参数,注意参数名需要相同 path('guest_detail/<int:guest_id>',views.guest_detail), views.py # 嘉宾列表 def guests(request): # 从数据库获取所有嘉宾 guest_list = Guest.objects.filter() # 返回页面并填充数据 return render(request,'guests.html',{'guest_list':guest_list}) # 嘉宾详情 def guest_detail(request,guest_id): # 根据获取的ID取对应的嘉宾 guest = Guest.objects.get(pk=guest_id) # 返回页面并且填充嘉宾 return render(request,'guest_detail.html',{'guest':guest})
最后:网页模板
guests.html
{% extends 'base.html' %} {% block content %} <ul class="list-group"> {% for guest in guest_list %} <li class="list-group-item text-center"><a href="/sgin/guest_detail/{{ guest.id }}">{{ guest.name }}</a></li> {% endfor %} </ul> {% endblock %}
guest_detail.html
{% extends 'base.html' %} {% block content %} <div class="panel-info panel"> <div class="panel-heading"> 嘉宾详情页 </div> <div class="panel-body"> <p>参与发布会:{{ guest.event }}</p> <p>姓名:{{ guest.name }}</p> <p>手机号:{{ guest.phone }}</p> <p>邮箱:{{ guest.email }}</p> <p>加入时间:{{ guest.join_time }}</p> <p><a href="/sgin/guests" class="btn btn-info">返回列表</a></p> </div> </div> {% endblock %}
18:Django数据库ORM知识梳理
1.数据库ORM 模型定义---字段类型与选项---需要牢记 2.数据库增删改查API 3.数据库表关联 4.Django自带后台管理系统
Django数据库ORM学习链接:
模型字段参考: https://docs.djangoproject.com/zh-hans/3.1/ref/models/fields/#field-types 数据库配置: https://docs.djangoproject.com/zh-hans/3.1/intro/tutorial02/ 数据库访问优化: https://docs.djangoproject.com/zh-hans/3.1/topics/db/optimization/ 字段命名限制: https://docs.djangoproject.com/zh-hans/3.1/topics/db/models/#field-name-restrictions 模型方法: https://docs.djangoproject.com/zh-hans/3.1/topics/db/models/#model-methods 模型管理器: https://docs.djangoproject.com/zh-hans/3.1/topics/db/managers/ ORM执行查询: https://docs.djangoproject.com/zh-hans/3.1/topics/db/queries/ QuerySetAPI参考: https://docs.djangoproject.com/zh-hans/3.1/ref/models/querysets/
19:持续开发
step1.为嘉宾模型添加is_sgin字段,用于记录是否签到 is_sign = models.BooleanField(default=False)
另外:修改发布会name使其唯一
step2 .同步模型数据库
manage.py makemigrations
manage.py migrate
第二步可能会抛出错误
原因是之前没有做唯一约束的时候,数据库里面发布会有同名的,所以操作数据库的时候出现了唯一约 束失败
解法方案:
进入数据库,把同名的数据删除掉,再执行migrate命令。
表单请求处理:
1.详情页增加签到表单,提交嘉宾手机号
知识点:表单基础 <form class="form-horizontal" action="/sgin/add_event" method="post" enctype="application/x-www-form-urlencoded"> <input type="text" class="form-control" placeholder="发布会名称" name="name"> <button type="submit" class="btn btn-block btn-info" >提交</button> </form>
三大必填属性:
action: 提交的URL
method: 请求方法,get或者post
enctype: 三种编码方式
application/x-www-form-urlencoded #键值对方式
multipart/form-data #二进制编码 上传文件会用到的方式
text/plain #文本方式
2:后台逻辑判断
接收表单数据,判断手机号 1.判断是否为有效的手机号码 2.判断手机号对应的用户是否为当前发布会嘉宾 3.判断该嘉宾的签到状态 整个流程参照下图
知识点1. 判断请求方法 request.method 值是大写的POST 或GET PUT DELETE 知识点2. 取post表单数据 request.POST[key] 或request.POST.get() # request.POST返回的是一个字 典 参考代码 def do_sgin(request,event_id): if request.method == 'POST': phone = request.POST['mobile'] # 签到流程处理
20:Django安全策略处理 CSRF
django的csrf防护处理方式(3选1)
1.自废武功:注释django.middleware.csrf.CsrfViewMiddleware
2:开后门:在不需要校验csrf的视图加上装饰器 from django.views.decorators.csrf import csrf_exempt @csrf_exempt def do_sgin(request,event_id): pass
3:走正规手续:表单内部加入{% csrf_token %} <form method="post" action="/sgin/do_sgin/{{ event.id }}" enctype="application/x-www-form-urlencoded"> {% csrf_token %} <input type="text" name="mobile" required> <button type="submit">输入手机号签到</button> </form>
Tips: csrf简介
1. CSRF(Cross-site request forgery)跨站请求伪造,是一种常见的网络攻击手段,具体内容和含义请大家自行百度。
2.Django默认为我们开启了防范 CSRF 攻击的机制。
3.对于GET请求,一般来说没有这个问题,通常是针对POST或PUT方法的
21:请求的转发 redirect(url)
页面跳转
手机号正常,状态正常,且签到状态修改成功后,跳转页面
如下:
采用重定向方式来进行页面跳转:
redirect(url) # url就是跳转的目标
22:继续开发【签到功能实现】
签到视图参考代码v1: def do_sgin(request,event_id): # 从post请求获取参数,首先判断请求方法 if request.method == 'POST': phone = request.POST.get('phone') # 判断手机号是否正确 res = Guest.objects.filter(phone=phone) if not res: return HttpResponse('手机号错误') guest = res[0] # 是否属于当前发布会 if guest.event.id != event_id: return HttpResponse('非当前发布会嘉宾') # 是否已经签到 if guest.is_sgin: return HttpResponse('已签到,不要重复签到') #进入签到 guest.is_sgin=True guest.save() return HttpResponse('收到手机号: '+phone+'签到成功')
签到路由: #签到 path('do_sgin/<int:event_id>',views.do_sgin)
签到表单页面---在原发布会详情页上修改: <form method="post" action="/sgin/do_sgin/{{ event.id }}" enctype="application/x-www-form-urlencoded"> {% csrf_token %} <input type="text" name="mobile" required> <button type="submit">输入手机号签到</button> </form>
优化:当前签到结果返回的页面太简陋,需要优化
1.制作专属签到成功页面
{% extends 'base.html' %}
{% block maintitle %}收到手机号: {{ phone }}, 签到成功!{% endblock %}
2.在发布会详情页,留下错误信息显示区 <form method="post" action="/sgin/do_sgin/{{ event.id }}" enctype="application/x-www-form-urlencoded"> {% csrf_token %} <input type="text" name="phone" required> <button type="submit">输入手机号签到</button> </form> {% if error %} <span class="btn btn-danger">{{ error }}</span> {% endif %} <p><a href="/sgin/events" class="btn btn-info">返回列表</a></p>
3.修改视图对应的返回 def do_sgin(request,event_id): # 从post请求获取参数,首先判断请求方法 if request.method == 'POST': phone = request.POST.get('phone') # 判断手机号是否正确 res = Guest.objects.filter(phone=phone) if not res: return render(request,'event_detail.html', {'event':Event.objects.get(pk=event_id),'error':'手机号错误'}) guest = res[0] # 是否属于当前发布会 if guest.event.id != event_id: return render(request,'event_detail.html', {'event':guest.event,'error':'非当前发布会嘉宾'}) # 是否已经签到 if guest.is_sgin: return render(request,'event_detail.html', {'event':guest.event,'error':'已签到,不要重复签到'}) #进入签到 guest.is_sgin=True guest.save() #return render(request,'sgin_success.html',{'phone':phone}) return redirect(f'/sgin/sgin_success/{phone}')
23:继续开发【新增嘉宾功能】
1:新增嘉宾视图创建 @csrf_exempt def add_guest(request): if request.method =='POST': #姓名 name = request.POST['name'] #手机号 phone = request.POST['phone'] #邮箱 email = request.POST['email'] #关联发布会 event_id = request.POST['event_id'] #创建嘉宾 try: guest = Guest.objects.create(name=name,phone=phone,email=email,event_id=event_id) except Exception as e: return render(request,'guest_add.html',{'error': repr(e)}) #返回精简错误信息 #保存成功-跳转到嘉宾列表页 return redirect('/sgin/guests/')
2:添加嘉宾--路由 path('add_guest/',views.add_guest)
3:新增嘉宾表单页面guest_add.html {% extends 'base.html' %} {% block content %} <div class="panel panel-info"> <div class="panel-heading"> 新增嘉宾 </div> <div class="panel-body"> <form method="post" action="/sgin/add_guest/" enctype="application/x-www-form-urlencoded"> {% csrf_token %} <div class="form-group"> <label class="col-sm-2 control-label">名称</label> <div class="col-sm-10"> <input class="form-control" type="text" name="name" required> </div> </div> <div class="form-group"> <label class="col-sm-2 control-label">手机号</label> <div class="col-sm-10"> <input class="form-control" type="text" name="phone" required> </div> </div> <div class="form-group"> <label class="col-sm-2 control-label">邮箱</label> <div class="col-sm-10"> <input class="form-control" type="text" name="email" required> </div> </div> <div class="form-group"> <label class="col-sm-2 control-label">发布会</label> <div class="col-sm-10"> <select class="form-control" name="event_id"> {% for event in events %} <option value={{ event.id }}>{{ event.name }} </option> {% endfor %} </select> </div> </div> <button class="btn btn-block btn-primary" type="submit">保存 </button> </form> {% if error %} <p class="danger"> {{ error }}</p> {% endif %} <p><a href="/sgin/guests" class="btn btn-info">返回列表</a></p> </div> </div> {% endblock %}
4:嘉宾列表增加入口 {% extends 'base.html' %} {% block maintitle %}<a href="/sgin/add_guest_page" class="btn btn-info navbarright">添加嘉宾</a>{% endblock %} {% block content %} <ul class="list-group"> {% for guest in guest_list %} <li class="list-group-item text-center"><a href="/sgin/guest_detail/{{ guest.id }}">{{ guest.name }}</a></li> {% endfor %} </ul> {% endblock %}
24:分页功能
分页的三要素
page_size: 每页显示的条数
page_num: 第几页
page_count: 总页数
Paginator对象:
方法:
Paginator.page(number) 返回指定页面的Page对象,比如第7页的所有内容,从1开始。如果提供的页码不存在,抛出InvalidPage异常。
Paginator.get_page(number) 上面方法的安全版本,不会弹出异常。如果输入的参数不是数字,返回第一页。如果输入的数字大于最大页码,返回最后一页。
属性:
Paginator.count:对象总数。
Paginator.num_pages:页面总数。
Paginator.page_range:基于1的页数范围迭代器。比如:[1, 2, 3, 4]
Page对象:Paginator.page(num)将返回一个Page对象,我们主要的操作都是基于Page对象的
方法:
Page.has_next():如果有下一页,则返回True。
Page.has_previous():如果有上一页,返回 True。
Page.has_other_pages():如果有上一页或下一页,返回True。
Page.next_page_number():返回下一页的页码。如果下一页不存在,抛出InvalidPage异常。
Page.previous_page_number():返回上一页的页码。如果上一页不存在,抛出InvalidPage异常。
Page.start_index() :返回当前页上的第一个对象,相对于分页列表的所有对象的序号,从1开始计数。 比如,将五个对象的列表分为每页两个对象,第二页的 start_index() 会返回3。
Page.end_index() :返回当前页上的最后一个对象,相对于分页列表的所有对象的序号,从1开始。 比如,将五个对象的列表分为每页两个对象,第二页的 end_index() 会返回4。
属性:
Page.object_list:当前页上所有对象的列表。
Page.number:当前页的序号,从1开始计数。
Page.paginator:当前Page对象所属的Paginator对象。
分页功能使用方法:以嘉宾列表为例,修改嘉宾列表视图,使其返回分页数据 # 嘉宾列表 def guests(request): # 从数据库获取所有嘉宾 guest_list = Guest.objects.filter() # 页面尺寸 - 每页显示的最大数量 page_size=3 # 定义分页器 Paginator(查询集的列表形式,页面尺寸) paginator = Paginator(list(guest_list),page_size) # 从请求中获取页码 page_index = request.GET.get('page') # 根据页码 显示对应页的数据 current_page = paginator.get_page(page_index) # 返回页面并填充数据 return render(request,'guests.html',{'guest_list':current_page})
修改嘉宾模板页面,添加分页控件 v1简单版分页器-- django自带效果 <div class="dataTables_paginate paging_simple_numbers"> <span class="step-links"> {% if guest_list.has_previous %} <a href="?page=1">« first</a> <a href="?page={{ guest_list.previous_page_number }}">previous</a> {% endif %} <span class="current"> Page {{ guest_list.number }} of {{ guest_list.paginator.num_pages }}. </span> {% if guest_list.has_next %} <a href="?page={{ guest_list.next_page_number }}">next</a> <a href="?page={{ guest_list.paginator.num_pages }}">last » </a> {% endif %} </span> </div>
v2-升级版分页器,采用模板提供样式 {% extends 'base.html' %} {% block maintitle %}<a href="/sgin/add_guest_page" class="btn btn-info navbarright">添加嘉宾</a>{% endblock %} {% block content %} <ul class="list-group"> {% for guest in guest_list %} <li class="list-group-item text-center"><a href="/sgin/guest_detail/{{ guest.id }}">{{ guest.name }}</a></li> {% endfor %} </ul> {# 开始分页 #} <div class="dataTables_paginate paging_simple_numbers" id="dataTablesexample_paginate"> <ul class="pagination"> {% if guest_list.has_previous %} <li class="paginate_button previous " aria-controls="dataTablesexample" tabindex="{{ guest_list.number }}" id="dataTables-example_previous"> <a href="?page={{ guest_list.previous_page_number}}">Previous</a> </li> {% else %} <li class="paginate_button previous disabled" aria-controls="dataTables-example" tabindex="0" id="dataTables-example_previous"> <a href="#">Previous</a> </li> {% endif %} {% for page_num in guest_list.paginator.page_range %} {% if page_num == guest_list.number %} <li class="paginate_button active " ariacontrols="dataTables-example" tabindex="0"><a href="#">{{ guest_list.number }} </a></li> {% else %} <li class="paginate_button " aria-controls="dataTablesexample" tabindex="0"><a href="?page={{ page_num }}">{{ page_num }}</a></li> {% endif %} {% endfor %} {% if guest_list.has_next %} <li class="paginate_button next" aria-controls="dataTablesexample" tabindex="0" id="dataTables-example_next"><a href="?page={{ guest_list.next_page_number }}">Next</a></li> {% else %} <li class="paginate_button next disabled" ariacontrols="dataTables-example" tabindex="0" id="dataTables-example_next"><a href="#">Next</a></li> {% endif %} </ul> </div> {% endblock %}
25:Django单元测试
Django测试组成:
Django 的单元测试采用 Python 的标准模块: unittest。该模块以 类 的形式定义测试。Django继承了该模块并对其做了一定修改,使其能配合Django的特性做一些测试。
主要测试对象:数据模型【model】
运行方法: python manage.py test
测试文件:app目录下的test.py
Django测试注意点:
使用Django提供的TestCase类作为被继承类。注意这里不是unittest的TestCase,否则无法使用django的测试功能。
创建数据时采用的是测试数据库,并且会在测试结束后销毁
测试行为不会影响到真实的数据库。
Django测试的实现:案例如下
from django.test import TestCase from .models import Event, Guest from datetime import datetime # Create your tests here. # 发布会测试 class EventTestCase(TestCase): def setUp(self): Event.objects.create(name='测试训练营1', address='软件大道', start_time=datetime.now(), limits=1000) Event.objects.create(name='测试训练营2', address='软件大道', start_time=datetime.now(), limits=500) def test_event_address(self): event1 = Event.objects.get(name='测试训练营1') event2 = Event.objects.get(name='测试训练营2') self.assertEqual(event1.address, '软件大道') self.assertEqual(event2.address, '软件大道') def test_event_limits(self): event1 = Event.objects.get(name='测试训练营1') event2 = Event.objects.get(name='测试训练营2') self.assertEqual(event1.limits, 1000) self.assertEqual(event2.limits, 500) def test_address_update(self): event1 = Event.objects.get(name='测试训练营1') event1.address = '花神大道' event1.save() self.assertEqual(event1.address, '花神大道') def test_delete(self): # 删前列出所有 event_list1 = Event.objects.all() event1 = Event.objects.get(name='测试训练营1') self.assertIn(event1, event_list1) # 检查 # 删除 event1.delete() # 删后列出所有 event_list2 = Event.objects.all() self.assertNotIn(event1, event_list2) # 校验结果 # 嘉宾测试 class GuestTestCase(TestCase): def setUp(self) -> None: # -> None 新语法 表示返回空 # 嘉宾需要关联发布会,所以创建发布会 self.event = Event.objects.create(name='测试训练营1', address='软件大道', start_time=datetime.now(), limits=1000) Guest.objects.create(name='小张', phone='13912345678', email='hello@test.com', event=self.event) def test_query(self): guest = Guest.objects.filter(name='小张')[0] self.assertEqual(guest.phone, '13912345678') def test_update(self): guest = Guest.objects.filter(name='小张')[0] guest.name = '小明' guest.save() guest2 = Guest.objects.filter(name='小明')[0] self.assertEqual(guest.phone, guest2.phone) def test_delete(self): guest = Guest.objects.filter(name='小张')[0] guest.delete() guest_list = Guest.objects.all() self.assertNotIn(guest, guest_list) def test_add(self): payload = { 'name': '小强', 'phone': '13712345678', 'email': 'xiaoq@test.com', 'event_id': self.event.id } # 模拟客户端发起请求 self.client.post('/sgin/add_guest/', data=payload) guest = Guest.objects.filter(name='小强')[0] self.assertEqual(guest.phone, '13712345678') """ 运行所有用例: python3 manage.py test 运行sign应用下的所有用例: python3 manage.py test sign 运行sign应用下的tests.py文件用例: python3 manage.py test sign.tests 运行sign应用下的tests.py文件中的 GuestTestCase 测试类: python3 manage.py test sign.tests.GuestTestCase ...... """
单元测试主要测试 模型 的基本功能,测试场景可能不够全面,所以大家了解这个功能即可。在项目中通 常使系统测试,集成测试,接口测试等方法对产品功能进行覆盖测试
26:数据库表关联多对多关系 ORM
多对多原理以及字段定义方法: 回顾下之前嘉宾与发布会:多个嘉宾对应一个,这多对1,那么这个情况和真实的不太相符,一个嘉宾也可以参与多个发布会,因此,改成多对多更合理
多对多与多对1的定义方式不同,注意对比 u外键的定义方式(多对1) models.ForeignKey(目标模型类, on_delete=models.CASCADE) 此外键定义在多的1方 u外键的定义方式(多对多)相对复杂, models.ManyToManyField(目标模型类) 只可以在1方定义,不可以两方同时定义
多对多应用实战: 修改发布会嘉宾的关系,变为多对多,外键定义在嘉宾这里,因为从业务来看,是嘉宾选择发布会,所以定义在嘉宾这里更合理
# 定义发布会关联嘉宾 class Guest(models.Model): # django会自动帮你创建一个id字段作为主键 # 关联发布会 # event = models.ForeignKey(Event,on_delete=models.CASCADE) #CASCADE 如果删除了关联的发布会,该嘉宾也会被删除 events = models.ManyToManyField(Event) 执行 python manage.py makemigrations 和python manage.py migrate 完成后,数据库会自动新增一个关系表,发布会和嘉宾之间的关系,就存储在这里 主张表的名字默认是 应用名_模型1_模型2 如: sgin_guest_events 关系发生变动后,视图和模板也需要进行相应的修改
View视图修改: 首先视图部分:add_guest 原来是关联1个发布会就够了,现在需要改成关联多个发布会,所以参数应该传递发布会的ID列表event_ids 更改前: # 关联发布会 event_id = request.POST['event_id'] 更改后: #关联发布会 #event_ids = request.POST['event_ids'] #这种方式只会获取最后一个值--错误方式 event_ids = request.POST.getlist('event_ids') #获取值列表 --正确方式 此时,新增嘉宾,多对多需要另外进行处理,
1:先创建嘉宾数据,
2:再将其进行关联 guest = Guest.objects.create(name=name,phone=phone,email=email) 创建嘉宾数据时,不要传入event_ids,应该用数据对象进行关联,方法是:
方法1: 数据对象.多方.add(多方数据对象1,多方数据对象2,多方数据对象3,.....) 可以简化成:数据对象.多方.add(*多方数据对象列表) # 利用自动解包可以省略手动传参
方法2: 数据对象.多方.set(多方数据对象列表) #根据event_ids查找发布会数据列表 events = [Event.objects.get(pk=event_id) for event_id in event_ids ] #将发布会数据列表关联到当前嘉宾 guest.events.add(*events) # 或者 guest.events.set(events)
# 签到视图修改 do_sgin: # 由于guest对应了多个发布会,所以不能通过guest.event来查看关联的发布会,应该通过guest.events.all() def do_sgin(request, event_id): # 从post请求获取参数,首先判断请求方法 if request.method == 'POST': phone = request.POST.get('phone') current_event = Event.objects.get(pk=event_id) # 判断手机号是否正确 res = Guest.objects.filter(phone=phone) if not res: return render(request, 'event_detail.html', {'event': current_event, 'error': '手机号错误'}) guest = res[0] # 是否属于当前发布会 event_ids = [d[0] for d in guest.events.values_list('id')] # 取出所有关联的发布会ID if event_id not in event_ids: return render(request, 'event_detail.html', {'event': current_event, 'error': '非当前发布会嘉宾'}) # 是否已经签到 if guest.is_sgin: return render(request, 'event_detail.html', {'event': current_event, 'error': '已签到,不要重复签到'}) # 进入签到 guest.is_sgin = True guest.save() return redirect(f'/sgin/sgin_success/{phone}')
模板修改 现在的添加嘉宾这里selected下拉框只能选择1个发布会,改成多选框 修改前: <select class="form-control" name="event_id"> {% for event in events %} <option value={{ event.id }}>{{ event.name }}</option> {% endfor %} </select> 修改后: <select class="form-control" name="event_ids" multiple> {% for event in events %} <option value={{ event.id }}>{{ event.name }}</option> {% endfor %} </select> 修改了name 增加了multiple属性
刷新页面创建一个嘉宾进行测试,发现成功。进入该嘉宾详情页,发现参与的发布会没有了 去对应的模板页面guest_detail.html查看: <p>参与发布会:{{ guest.event }}</p> 这里还是用之前的属性 更改后: <p>参与发布会:{% for event in guest.events.all %}<span>{{ event }}</span>{% endfor %}</p>
扩展:关联发布会加上超链接: <p>参与发布会:{% for event in guest.events.all %}<a href="/sgin/event_detail/{{event.id }}"> {{ event }} |</a>{% endfor %}</p>
多对多中间表:
仔细观察发布会关联嘉宾这里的模型定义
# 加入时间 --创建数据的时候就自动取当前时间 auto_now_add=True
join_time = models.DateTimeField(auto_now_add=True)
# 是否签到
is_sgin = models.BooleanField(default=False)
之前的嘉宾和发布会是多对 1的关系,那么1个嘉宾 保存对应的 加入时间 和 签到状态 是没有问题的,
但 是现在改成了多对多,那么数据还这样存储的话就会出现 嘉宾对关联的发布会1 进行签到,但是再签到 发布会2,3的时候发现也签到了。
因为签到状态绑定在了嘉宾这边,修改了签到状态就相当于把所有发 布会都签到了,这显然不合理。同样加入时间也是一样
那么这两个这个字段应该定义在哪里呢,仔细想想表示嘉宾和发布会两表之间的关系在哪? 【中间表】
在中间表这里再添加两个字段,重新定义该中间表。由于我们之前没有显示定义该表,这个表 是自动创建的。现在我们显示定义该表
#sgin/models.py 【中间表】 class GuestEvent(models.Model): # 通过外键关联对应数据,on_delete设置为删除对应的数据即删除该条记录,如发布会1对应嘉宾2 # 当发布会1或嘉宾2任意一个被删除,这个条对应关系也就不应该存在了 event = models.ForeignKey(Event,verbose_name='发布会',on_delete=models.CASCADE) guest = models.ForeignKey(Guest,verbose_name='嘉宾',on_delete=models.CASCADE) # 加入时间 --创建数据的时候就自动取当前时间 auto_now_add=True join_time = models.DateTimeField(auto_now_add=True) # 是否签到 is_sgin = models.BooleanField(default=False)
修改发布会: # 定义发布会关联嘉宾 class Guest(models.Model): # django会自动帮你创建一个id字段作为主键 # 关联发布会 # event = models.ForeignKey(Event,on_delete=models.CASCADE) #CASCADE 如果删除了关联的发布会,该嘉宾也会被删除 events = models.ManyToManyField(Event,through='GuestEvent') # through=中间表名# 姓名 字符串 64 唯一 name = models.CharField(max_length=64,unique=True) # 手机号 字符串 11 唯一 phone = models.CharField(max_length=11,unique=True) # 邮箱 邮箱格式 xxx@yyy.zz email = models.EmailField()
这时候运行migrate时提示错误:
ValueError: Cannot alter field sgin.Guest.events into sgin.Guest.events -
they are not compatible types (you cannot alter to or from M 2M fields, or add or remove through= on M2M fields)
修改表名-- 应用名_模型_多对对关联字段 和默认创建的表名相同
class Meta: # 元类 作用是可以设置模型信息
db_table = "sgin_guest_events" #模型对应的表名
原迁移文件是要创建一个表,但是现在我的数据在 刚刚默认 创建的 中间表 中,因此只需要修改 中间表 字段就可以,当我们要修改数据库中已存在的表,就要修改迁移文件了。这里属于高端操作,如果我们一 开始就定义数据模型,那么这一步可以不需要 修改最新的迁移文件(运行makemigrations命令生成的最新文件,在应用程序/migrations下面)
#修改 state_operations = [ migrations.RemoveField( model_name='guest', name='is_sgin', ), migrations.RemoveField( model_name='guest', name='join_time', ), migrations.CreateModel( name='GuestEvent', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('join_time', models.DateTimeField(auto_now_add=True)), ('is_sgin', models.BooleanField(default=False)), ('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sgin.event', verbose_name='发布会')), ('guest', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sgin.guest', verbose_name='嘉宾')), ], options={ 'db_table': 'sgin_guest_event', }, ), migrations.AlterField( model_name='guest', name='events', field=models.ManyToManyField(through='sgin.GuestEvent', to='sgin.Event'), ), #指定修改的数据库 migrations.AlterModelTable( name='GuestEvent', # 中间表的模型名 table='sgin_guest_events' # 当前数据库中的中间表名 ) ] # 重新定义数据库操作 operations = [ #分离数据库状态 migrations.SeparateDatabaseAndState(state_operations=state_operations), # 新增join_time字段 migrations.AddField( model_name='GuestEvent', name='join_time', field=models.DateTimeField(auto_now_add=True), ), # 新增is_sgin字段 migrations.AddField( model_name='GuestEvent', name='is_sgin', field=models.BooleanField(default=False), ), migrations.AlterModelTable( name='GuestEvent', #不创建实际的表 table=None, ), ]
视图修改: def do_sgin(request,event_id): #.....前置代码 # 是否已经签到 ge=GuestEvent.objects.get(guest_id=guest.id,event_id=event_id) if ge.is_sgin: return render(request,'event_detail.html', {'event':current_event,'error':'已签到,不要重复签到'}) #进入签到--操作中间模型 ge.is_sgin=True ge.save()
27:ORM数据库事务 with transaction.atomic():
创建嘉宾的代码: guest = Guest.objects.create(name=name,phone=phone,email=email) #根据event_ids查找发布会数据列表 events = [Event.objects.get(pk=event_id) for event_id in event_ids ] #将发布会数据列表关联到当前嘉宾 # guest.events.add(*events) guest.events.set(events)
如果创建嘉宾的时候没有问题,而查找发布会或关联发布会的步骤出现了问题,就会出现嘉宾创建 了,但是没有关联到发布会,这是一个bug
们应该这样处理,要么一次性成功创建且关联到发布会,要么失败不创建 【事务性】
在数据库中这种要成功就一起成功,要失败就一起失败的做法叫 事务,如果这里嘉宾创建成功但是后面 的步骤失败了,那么数据库会发起回滚操作,将创建的嘉宾撤销。
在django中,可以用with transaction.atomic(): 语句块实现
from django.db import transaction with transaction.atomic(): #事务操作。。。
创建嘉宾代码部分可以 改成 事务模式: #创建嘉宾 try: with transaction.atomic(): guest = Guest.objects.create(name=name,phone=phone,email=email) #根据event_ids查找发布会数据列表 events = [Event.objects.get(pk=event_id) for event_id in event_ids ] #将发布会数据列表关联到当前嘉宾 # guest.events.add(*events) guest.events.set(events) except Exception as e: return render(request,'guest_add.html',{'error': repr(e)}) #返回精简错误信息 #保存成功-跳转到嘉宾列表页 return redirect('/sgin/guests/')
28:Django内容详解
Django详解: python解释器安装的 库 和 全局 是没有影响的 不同项目依赖的版本不一致,为了杜绝版本依赖的问题,每个项目的依赖都是独立的 创建虚拟的python环境:python里默认安装的库都是 全局 的,不同版本会造成干扰,手动创建虚拟环境,让不同项目都有自己的 独立空间,环境也独立---手动创建虚拟环境 python -m venv djenv: 创建一个 解释器 环境还包含所依赖的库文件,最基础的库文件,可以自己扩展安装其他的库 python3自带 venv 模块 djenv:虚拟环境的名称 为了让项目所依赖的环境隔离,因为不同项目可能依赖不同环境和不同的库文件版本,所以创建虚拟环境,隔离副本,不影响其他项目 djenv\Scripts\activate.bat:激活虚拟环境 激活后命令行的右侧出现(djenv),表示已经进入虚拟环境了 进入虚拟环境后就可以在虚拟环境安装Django等各种库:pip install Django start project 项目创建:创建Django项目 python -m django startproject <projectname> 创建项目的目录 django-admin startproject <projectname> 创建项目的目录,这个需要处于Django 命令空间才能使用 现在就可以使用pycharm 打开创建的Django项目 projectname:项目工程目录 projectname:和项目工程目录名字一样,这是工程配置文件所在子目录,项目的名字,这个名称不能修改,而项目工程目录名称可以修改的 djangosite:项目工程目录 djangosite:项目的名字 settings.py:配置,主配置文件 asgi.py:服务器配置文件,异步配置文件 网关接口 wsgi.py:服务器配置文件,同步,python 2只有这个没有aasgi.py 网关接口 urls.py:总路由文件,主路由文件 manage.py:项目管理脚本 demo:创建的应用程序 djenv:虚拟的解释器环境 脚本存放的目录,脚本执行调用的解释器环境 分清楚 cd djangosite (外面的djangosite) django-admin startapp demo 创建一个应用程序,写的项目的逻辑都在这个demo文件里,应用程序的名字就叫demo Django一个项目里可以有多个应用程序的,多个应用程序都是可插拔,应用程序写好了可以直接移植到其他的Django项目里 创建了demo文件夹: admin.py:后台管理 apps.py: models.py:数据模型 tests.py:默认编写测试用例的 views.py:视图 这些文件都可以改,Django目前提供的最简单的参考,简单架构 创建了demo web应用程序之后,把web应用程序注册到djangosite项目的sitting配置文件里,里面就是配置多个应用程序的 配置到 INSTALLED_APPS 这一项,不配置后面可能遇到操作模板找不到各种问题,操作数据也可能出现问题 这样项目才知道这个应用程序,把应用程序下面的逻辑使用过来 除了注册自定义的应用程序 demo,还有django自带的应用程序,帮助开发web应用的 Django项目的启动: 1:djenv\Scripts\activate.bat 进入虚拟环境 2:cd djangosite 进入项目文件,有一个manager.py文件 3:python manage.py runserver 启动整个服务 运行后本地运行一个服务,默认监听 8000 端口 http://127.0.0.1:8000/ 输入这个地址可以直接访问,默认界面 Django总结: 路由定义的时候 习惯末尾 加 / 如果定义了 /: 那么浏览器访问的时候可以不带,浏览器自动补全 请求工具需要自己带斜杠,因为工具不会补全 如果没有定义 /: 那么浏览器访问时候不带 / 工具请求也不带 /
Django单元测试: Django测试组成: Django 的单元测试采用 Python 的标准模块: 内置unittest框架。该模块以类的形式定义测试。Django继承了 该模块并对其做了一定修改,使其能配合Django的特性做一些测试。 主要测试对象:数据模型 model模型 运行方法: python manage.py test 测试文件:app目录下的test.py 单元测试的用例文件写在 应用程序目录下tests文件里 运行方法: 1:python manage.py test 这样会运行tests.py模块下的用例,运行所有app应用下的tests.py下的所有的测试用例 2:python manage.py test sgin 指定运行 sgin这个app应用下的测试用例 3:python manage.py test sgin.tests 指定到 sgin 应用下的 tests文件 4:python manage.py test sgin.tests.GuestTestCase 指定到 sgin 应用下的 tests文件 里的 GuestTestCase 类 5:python manage.py test sgin.tests.GuestTestCase.test_delete 执行单条测试用例,某个函数 Django测试注意点: 使用Django提供的TestCase类作为被继承类。注意这里不是unittest的TestCase,否则无法使用django的测试功能 TestCase类是Django封装过了的,需要继承这个类才能使用Django提供的一些功能 测试模型会产生数据,继承了测试模型的测试类后采用的数据会临时创建一个测试环境的数据库,测试完成后测试数据库会被销毁,测试行为不会影响到真实数据 创建数据时采用的是测试数据库,并且会在测试结束后销毁 测试行为不会影响到真实的数据库 单元测试主要就是测试models模型 api的地方,比python shell好用,测试完成的数据不会影响真实数据,测试ORM这些api 详情查看 sgin 下面的 tests.py文件
djangosite - > sgin|demo|djangosite from datetime import datetime from django.test import TestCase from sgin.models import Event, Guest # Create your tests here. # 发布会模型测试 class EventTestCase(TestCase): """测试类的名称没有要求,只要继承TestCase就可以了""" def setUp(self) -> None: # 箭头表示返回值的一个类型,python3.6新加的功能,表示这个 setUp 函数返回值是一个None # 如果这 return "" 返回一个str类型解释器会有错误提示,但是编译器不影响解读和执行,运行不影响,避免代码少些错误 # 准备发布会数据 Event.objects.create(name="测试训练营1", address="德玛西亚", limits=300, start_time=datetime.now()) Event.objects.create(name="测试训练营2", address="德玛西亚", limits=100, start_time=datetime.now()) def test_event_address(self): """测试查询功能,用例测试发布会的地址""" event1 = Event.objects.get(name="测试训练营1") event2 = Event.objects.get(name="测试训练营2") assert event1.address == "德玛西亚" assert event2.address == "德玛西亚" def test_event_limits(self): event1 = Event.objects.get(name="测试训练营1") event2 = Event.objects.get(name="测试训练营2") assert event1.limits == 300 assert event2.limits == 100 # 嘉宾测试 class GuestTestCase(TestCase): def setUp(self) -> None: # 准备发布会数据 event = Event.objects.create(name="测试训练营3", address="德玛西亚", limits=300, start_time=datetime.now()) Guest.objects.create(event=event, name="叶孤城", phone="19999999999", email="19999999999@qq.com") # 因为嘉宾对象需要外键关联发布会,所以这里需要传一个event发布会的数据对象,或者 event_id 两者都可以 def test_update(self): guest = Guest.objects.get(phone="19999999999") guest.name = "阳顶天" # guest.save() # 注释这个数据也会执行成功,数据库不保存数据,但是上面 guest.name = "阳顶天" 已经把值保存到对象里了, # 下面 assert guest.name == "阳顶天" 再从对象里取值直接是从内存里访问这个值的,没有访问数据库 ---这条用例有问题 # assert guest.name == "阳顶天" 不要这样校验,这样只是校验内存里的值,没有从数据库里查,需要再从数据库里再查一下 guest2 = Guest.objects.get(phone="19999999999") assert guest.name == guest2.name # 如果改成功了必然相等的,save注释就不会通过了 def test_delete(self): guest = Guest.objects.get(phone="19999999999") # 删除 guest.delete() # 删除数据,delete后是真的删除了,删除后去查下一下 guest_list = Guest.objects.all() assert guest not in guest_list # 断言不存在了 # all()返回一个QuerySet,QuerySet是一个类似列表类型的数据,判断数据在不在列表里使用 in或者not in
Django安全策略:Django默认开启csrf: Django安全策略处理: django的csrf防护处理方式(3选1) csrf:跨站请求伪造,是一种常见的网络攻击手段 django对post提交的请求默认开启了一个防范手段,只使用get方法不提交数据一般没什么问题,使用post或者put请求体提交数据加了csrf Django的csrf防护处理方式: 1.自废武功 注释django.middleware.csrf.CsrfViewMiddleware 在sitting文件里 CsrfViewMiddleware 中间件 2.开后门:在不需要校验csrf的视图加上装饰器 在post请求的view试图函数加 @csrf_exempt 装饰器,这里也就是 do_sgin 函数 3:走正规手续 表单内部加入{% csrf_token %},随便加在表单任意的一处,开头和结尾都可以 <form class="form-horizontal" action="/sgin/do_sgin/{{ event.id }}" method="post" enctype="application/x-www-form-urlencoded"> <input type="text" class="form-control" placeholder="请输入手机号" name="phone"> <button type="submit" class="btn btn-block btn-info" >签到</button> {%csrf_token%} </form>
Django自带的admin后台: Django自带的后台数据管理 用 API 手动创建的方式还是太麻烦,后续我们可以用API编写到接口里面 现在,我们先用Django自带的后台来管理操作数据 1. 模型注册到 admin.py 2. 创建管理员用户 python manage.py createsuperuser 3. 新增数据 Django的admin后台地址:http://127.0.0.1:8001/admin/login/?next=/admin/ 注册超级用户: python manage.py createsuperuser 命令行下面执行:(djenv) C:\Users\Autel\Desktop\course_python\djangosite>python manage.py createsuperuser 当前注册的用户:admin 123456 创建成功会可以使用用户名和密码登录 进入界面后查看: Site administration下只有两个表 Groups Users 这两个表不是sign这个app应用的,在后台看到sign里面,需要admin 关联模型,admin.py 在应用程序sgin和demo的admin.py文件进行配置 from django.contrib import admin from sgin import models # Register your models here. # 注册模型,注册Event模型,数据操作模型 admin.site.register(models.Event) 然后查看http://127.0.0.1:8001/admin/login/?next=/admin/ 可以看到Sgin下一条记录Event,可以点击进去查看,每个记录对应数据库表里一条数据,可以直接修改里面的数据点击save 通过Django后台自带的管理页面操作保存数据,Mysql也可以使用admin关联 因为Event的__str__函数,数据对象对外的名称由 "name"代替了,所以Django后台自带的管理页面能看到 "name" 创建数据--发布会
Django视图与路由: MVC: Django里叫MTV Django视图: 负责给用户展示信息的,可以直接处理客户端发送过来的请求 浏览器发送请求给服务器,Django框架下通过view来接收用户请求,Temp模板可以暂时绕过,直接向用户进行消息返回 view: 一个视图(view)是python中针对一个特定的url的回调函数,此回调函数描述了需要展示的数据 在应用程序 demo 的view.py文件定义视图
Django请求转发重定向: do_sgin视图函数处理完成之后页面跳转到另外一个路由上面,不停留在当前页面---请求重定向 301:永久重定向 302:临时重定向 把原本访问A的请求转给B Django快捷方法: redirect(视图,参数) # 对于状态码302
Django项目小结:
功能点:
发布会
嘉宾
视图与路由
Django ORM技术:模型定义 API增删改查,多对1,多对多
模板技术 Models 后面用得少,暂时使用
路由转发:redirect 次要技术点
sqllite的使用: 查看 djangosite项目文件下的 settings.py 的 DATABASES设置 1:'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': BASE_DIR / 'db.sqlite3', } ENGINE:驱动 NAME:数据库的名字,sqllite文本类型数据库,知道路径就行 2:INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'demo', # 注册app应用程序 'sgin', # 注册app应用程序 ] 关联数据所在的app,数据写在sgin里,不写检测不到 其他详情查看
发布会功能迭代: 第一期: 1:更新event视图,使其查询的是真实的发布会数据 2:更新发布会视图详情 根据发布会ID展示发布会的信息 发布会url可以设计成 event_detail/<int:event_id> 第二期:需求描述 1:创建嘉宾数据模型对象 手机号,姓名,邮件,关联发布会 2:模仿发布会列表和详情页,创建嘉宾视图路由以及模板页面 /sgin/guests/ 嘉宾列表 /sgin/guest_detail/<int:id> 嘉宾详情页
多级路由转发 路由文件:项目根目录 djangosite 的配置文件所在的目录, urls.py:总的路由文件 项目简单的时候路由写在一起,项目复杂的时候视图不会只有一个urls.py文件 多的时候写在一个文件显得项目臃肿,不好维护,路由分解 不同应用程序各自的自身的路由放到自己的文件里,然后主路由里做一个分发,主路由文件配置分发到子路由文件,不同这块代表不同web应用程序路由文件 子路由文件,子路由文件再编写路径与视图的关系 路由配置方式一:在djangosite 项目文件里找到 urls.py文件 如下配置: 路由的配置可以如下这么编写 rom demo import views as demo_views from sgin import views as sgin_views path('index/', demo_views.index), # demo里的路由 path('home/', demo_views.home), # demo里的路由 path('events/', sgin_views.event), # 发布会管理页面 sgin里的路由 path('events/detail/', sgin_views.events_detail), # 发布会详情页 sgin里的路由 这样把路由和视图函数写在一起不利于维护,应该在不同的应用程序下面建立小的路由文件 路由配置方式二:多级路由配置,在不同应用程序下面建立小的 url文件,也可以使用其他名字,建议使用url,不容易出错 在 demo和 sgin目录下都创建 url.py 这个文件
数据库表关联: 数据库表管理之 1对多,多对1,1对1详情查看 models.py 文件 正向查询 反向查询 多对1:发布会1 对应 嘉宾多 一个发布会关联多个嘉宾,一个嘉宾只能参加一个发布会 多对多:一个发布会可以关联多个嘉宾,一个嘉宾也可以关联多个发布会 模型关系升级:从多对1 升级到 多对多 外键的定义方式(多对1): models.ForeignKey(目标模型类, on_delete=models.CASCADE) 此外键定义在多的1方 外键的定义方式(多对多)相对复杂 models.ManyToManyField(目标模型类) 只可以在1方定义,不可以两方同时定义 发布会和嘉宾两边都是多 发布会这里写个嘉宾字段,字段类型 ManyToManyField,指向嘉宾 嘉宾这也可以定义一个event字段,ManyToManyField类型,指向发布会 不要两个都写,只写一个,写一个就知道这两个表是多对多的关系,不需要重复定义 做开发的时候根据常理做推断,如果只能定义在一方一般定义在 主动发起关联的一方 当前模型 是 嘉宾主动关联发布会,比如创建嘉宾的时候主动关联几个发布会比较简单 但是创建发布会的时候主动关联嘉宾需要操作的数据量太大 ----当前模型定义在多对多关系放在嘉宾这一方 1:修改Guest模型的数据关联 event = models.ManyToManyField(Event) # ManyToManyField:多对多的模型关系,由 多对1改成多对多 # ManyToManyField不需要设置 on_delete了,一个发布会删了嘉宾还可以关联其他发布会,不需要处理 on_delete事件,一个指向关联的模型数据就行 2:同步修改后的数据模型关系 python manage.py makemigrations python manage.py migrate 3:查看数据库信息 表sgin_guest和sgin_event的数据没有变化 sgin_guest客户表的结构少了一个字段,少了外键关联的event字段 多了一张表:sgin_guest_event 中间表 中间表 数据库表管理之 多对多 多对1:外键定义在多方 多对多:对应映射关系不存在发布会表也不存储在嘉宾表,找一张中间表来保存关联关系,中间表定义发布会和嘉宾之间的关系 当前模型的中间表 3个字段 id guest_id:嘉宾id event_id:发布会id 一条数据就是一个对应关系:比如嘉宾8 关联了 发布会 1 多对多可以几个表之间关联,关系更加复杂 多对多模型中: 1:把签到关系is_sgin 是否签到从 guest 嘉宾表移动到 sgin_guest_event 中间表里 中间表增加一个字段 is_sgin 中间表 自动创建的时候只有 guest_id 和 event_id 两个字段 我们在 models定义一个中间表 模型 详情仓库 models文件中自定义的 GuestEvent 类 2:这时候有两个中间表: sgin_guest_event 自动创建的 sgin_guestevent 自定义的 3:修改一下表名:使用 meta 元类 多对多表数据查询: 类似多对1的反向查询 正向查询: guest 查询 event guest.event.all() 返回QuerySet对象 格式:数据对象.多方属性.all() guest.event 会返回一个多对多管理器,用法和模型管理器一样,可以使用 filter,get,all等相关方法 反向查询: event 查询 guest event字段是定义在Guest类里的,可以guest.event这样正向查询 发布会查询其关联的嘉宾数据:event.guest_set.all() 返回QuerySet对象 格式:数据对象.多方属性_set.all() 数据对象.多方模型小写_set.all() event.guest_set会返回一个多对多管理器,用法和模型管理器一样,直接调用 filter,get,all等相关方法 当前创建嘉宾和发布会的关联两步走的 1:创建嘉宾 guest = Guest.objects.create(name=name, phone=phone, email=email) # 创建嘉宾 2:嘉宾关联N个发布会 guest.event.add(*event_list) 发布会是已有的数据,新增嘉宾后再关联 创建嘉宾OK,关联发布会如果关联不上 ---- 涉及到 mysql 事务 操作 如上比如 1 操作 ok 2操作失败 那么嘉宾创建了但是没有关联上,会有Bug,正常工作中 步骤1:创建嘉宾 步骤2:嘉宾关联发布会 是一个事务关系 要么都成功,要么都失败,处理关联性很强的动作使用事务进行包裹 比如嘉宾创建成功了但是关联操作失败了,应该将数据库进行回滚,创建动作撤销 with transaction.atomic(): 事务操作 创建嘉宾 + 关联操作会
数据操作方式-ORM和SQL: 方式一:原生sql 方式二:ORM(对象关系映射)工具,简单的api完成,很多web框架都会自带ORM,JAVA spring用的JPA等 Django自带ORM,用来操作数据库的工具 ORM原理和基础: ORM用来操作数据库的一个工具 原理:Object Relational Mapping(对象关系映射) 其主要作用是在编程中,把面向对象的概念跟数据库中表的概念对应起来。当然这只是从对象到SQL的映射,还有从SQL到对象的映射,也是类似的过程。 表——>表里面有数据 表结构一般是不变的,一条条数据是可能变化的,字段的值可能变化,那些字段是可以确定的 表结构看成类,数据就是对象 ORM通过这个原理操作数据 拿到一个数据其实就是拿到一个对象,编程语言调用很方便,接口自己调用就行 短期之内不需要掌握复杂的sql,调用见到的api来操作sql ---- 这就是ORM ORM的使用: 0:先有个数据库的表 定义一个类,web开发里叫 model,模型的意思,一条条数据对应 对象 和 实例 1:数据模型类定义在app应用程序目录下的model.py中 (Django 应用创建的时候默认创建就 model.py 文件) 2:模型类继承 Django 的 models.Model 模型基本类 3:类名对应 表名称,实际是 小写应用名_小写模型类名 4:类成员对应数据的字段类型 sgin:发布会例子为实例 发布会模型定义 event,参考sign.models.py 发布会名称:字符串 最大长度256 非空 发布会地点:字符串 最大长度256 非空 发布会人数:整型 默认为100 发布会状态:布尔类型,默认为True 开始时间:日期时间类型,python里的datetime.datetime的实例 详情查看 sgin ——> models.py 文件
29:项目代码
1:项目的目录结构

NONE

NONE

from django.contrib import admin # Register your models here.

from django.apps import AppConfig class DemoConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'demo'

from django.db import models # Create your models here.

from django.test import TestCase # Create your tests here.

from django.contrib import admin from django.urls import path # from . import views # 从当前目录导入views模块 from demo import views # 根目录导入view视图文件 # 子路由列表 urlpatterns = [ path('index/', views.index), path('home/', views.home), ]

from django.shortcuts import render from django.http import HttpResponse # Create your views here. """ 视图函数:处理用户的请求 视图函数 必须接收的参数:request,取其他名字也可以,形参的名字不重要, request代表用户的请求,用户请求的所有数据都在这里面,比如请求的url,请求的参数,cookie等都在这里 Django对request做一个封装,可以获取到当前登录的用户,请求方法,都是通过request对象获取的 请求信息都在request,可以不用,必须定义这个request参数 视图函数 的返回值: 必须返回一个HttpResponse,返回一个响应对象,直接返回一个字符串报错 响应对象的包装: 1:最简单的方式: HttpResponse包装 from django.http import HttpResponse 视图函数:return HttpResponse("Hello World") 这个只是简单的返回字符串 设置映射关系:Django里叫视图的向导,也叫做路由 比如:/sign/enentmgr --> ebebtmgr视图 访问一个什么地址,这个地址被应用程序转发给某个视图函数去处理,在前端的里 请求的路径被什么视图函数来处理,视图函数也就是在view里面定义的函数 定义路由: djangosite项目文件的 urls.py 文件里配置 Django服务启动后: 1:djangosite项目文件的 urls配置路由,也就是访问的url映射到的视图函数 2:启动Django服务器 3:web服务器访问:http://127.0.0.1:8000/index/ 就能调用view视图函数里的 index 函数了 """ def index(request): return HttpResponse("Hello World") def home(request): return HttpResponse("<h1>这里是主页</h1>")

NONE

""" ASGI config for djangosite project. It exposes the ASGI callable as a module-level variable named ``application``. For more information on this file, see https://docs.djangoproject.com/en/4.1/howto/deployment/asgi/ """ import os from django.core.asgi import get_asgi_application os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'djangosite.settings') application = get_asgi_application()

""" Django settings for djangosite project. Generated by 'django-admin startproject' using Django 4.1.5. For more information on this file, see https://docs.djangoproject.com/en/4.1/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/4.1/ref/settings/ """ from pathlib import Path # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = 'django-insecure-b&xyy(jm8e2n1svup$9mt+8dx(--nab#@hw8vl8t$3ta-%(%!b' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True ALLOWED_HOSTS = [] # Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'demo', # 注册app应用程序 'sgin', # 注册app应用程序 ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] ROOT_URLCONF = 'djangosite.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] WSGI_APPLICATION = 'djangosite.wsgi.application' # Database # https://docs.djangoproject.com/en/4.1/ref/settings/#databases DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': BASE_DIR / 'db.sqlite3', } } # Password validation # https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', }, { 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', }, { 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', }, { 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', }, ] # Internationalization # https://docs.djangoproject.com/en/4.1/topics/i18n/ LANGUAGE_CODE = 'en-us' TIME_ZONE = 'UTC' USE_I18N = True USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/4.1/howto/static-files/ STATIC_URL = 'static/' # Default primary key field type # https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

"""djangosite URL Configuration The `urlpatterns` list routes URLs to views. For more information please see: https://docs.djangoproject.com/en/4.1/topics/http/urls/ Examples: Function views 1. Add an import: from my_app import views 2. Add a URL to urlpatterns: path('', views.home, name='home') Class-based views 1. Add an import: from other_app.views import Home 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') Including another URLconf 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import admin from django.urls import path, include from demo import url as demo_url # 导入子路由模块 from sgin import url as sgin_url urlpatterns = [ path('admin/', admin.site.urls), # path: 参数1:访问的路径 参数2:对应的视图函数,记住不要加()去调用index函数,传index函数即可 # 1:index前面不需要写 "/"了,默认会加的,后面的 "/" 如果定义了访问的时候必须加上,浏览器不加会自动补全的,最终访问的时候还是需要加上 "/" # 添加好了路由滞后,根据设置好的路由关系把请求丢给对应的视图函数index去处理 # 当我们脚本更新之后服务段会做一个自动更新,就是项目里的内容加上变化了服务器就自动更新 # 写法一:直接在1级路由里填写全部的view视图函数 # 路由的配置可以如下这么编写 # from demo import views as demo_views # from sgin import views as sgin_views # path('index/', demo_views.index), # demo里的路由 # path('home/', demo_views.home), # demo里的路由 # path('events/', sgin_views.event), # 发布会管理页面 sgin里的路由 # path('events/detail/', sgin_views.events_detail), # 发布会详情页 sgin里的路由 # 这样把路由和视图函数写在一起不利于维护,应该在不同的应用程序下面建立小的路由文件 # 写法二:多级路由模式 # 多级路由配置,path指向应用程序demo和sgin # include:包含 path('demo/', include(demo_url)), # '':表示默认地址,不能加 /,子路由的路径没有任何前缀斜杠什么的,父路由就不用写了直接 '',写了反而匹配不到 # 或者写一个应用程序的名称 demo/ 来匹配访问应用程序下的view,网页访问的路由就变成了:http://127.0.0.1:8001/demo/index/ # http://127.0.0.1:8001/一级路由地址(项目文件下url文件里的地址)/二级路由地址(也就是应用程序下url文件里的地址)/ path('sgin/', include(sgin_url)), ]

""" WSGI config for djangosite project. It exposes the WSGI callable as a module-level variable named ``application``. For more information on this file, see https://docs.djangoproject.com/en/4.1/howto/deployment/wsgi/ """ import os from django.core.wsgi import get_wsgi_application os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'djangosite.settings') application = get_wsgi_application()

ORM数据库同步的临时文件: 内容模板: # Generated by Django 4.1.5 on 2023-02-01 02:13 from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('sgin', '0005_remove_guest_is_sgin_remove_guest_join_time_and_more'), ] state_operations = [ migrations.CreateModel( name='GuestEvent', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('join_time', models.DateTimeField(auto_now_add=True)), ('is_sgin', models.BooleanField(default=False)), ('event', models.ForeignKey(on_delete=models.deletion.CASCADE, to='sgin.event', verbose_name='发布会')), ('guest', models.ForeignKey(on_delete=models.deletion.CASCADE, to='sgin.guest', verbose_name='嘉宾')), ], options={ 'db_table': 'sgin_guest_event', }, ), migrations.AlterField( model_name='guest', name='events', field=models.ManyToManyField(through='sgin.GuestEvent', to='sgin.Event'), ), #指定修改的数据库 migrations.AlterModelTable( name='GuestEvent', # 中间表的模型名 table='sgin_guest_event' # 当前数据库中的中间表名 ) ] # 重新定义数据库操作 operations = [ #分离数据库状态 migrations.SeparateDatabaseAndState(state_operations=state_operations), # 新增join_time字段 migrations.AddField( model_name='GuestEvent', name='join_time', field=models.DateTimeField(auto_now_add=True), ), # 新增is_sgin字段 migrations.AddField( model_name='GuestEvent', name='is_sgin', field=models.BooleanField(default=False), ), migrations.AlterModelTable( name='GuestEvent', #不创建实际的表 table=None, ), ]

前端文件,不知

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>404 Not Found</title> </head> <body> <h1>你要找的数据不存在</h1> </body> </html>

{% load static %} {# 使用static这些静态文件需要对模板文件进行修改: 需要告诉文件需要使用静态文件,在开头加load static #} {# 引入css <link href="{% static 'assets/css/bootstrap.css' %} " rel="stylesheet" />#} {# static:告诉Django模板加载静态页面的时候去那个目录下寻找,这里表示去应用程序下的static文件下寻找 #} {# js的引入和css类似:<script src="{% static 'assets/js/jquery-1.10.2.js' %}"></script>#} <!DOCTYPE html> <html lang="cn"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta content="haiwen" name="author" /> <!-- Bootstrap Styles--> <link href="{% static 'assets/css/bootstrap.css' %} " rel="stylesheet" /> <!-- FontAwesome Styles--> <link href="{% static 'assets/css/font-awesome.css'%}" rel="stylesheet" /> <!-- Custom Styles--> <link href="{% static 'assets/css/custom-styles.css' %}" rel="stylesheet" /> <title>{% block title%}{% endblock %}</title> </head> <body> <div id="wrapper"> <nav class="navbar navbar-default top-navbar" role="navigation"> <div class="navbar-header"> <a class="navbar-brand"><i class="icon fa fa-plane"></i> 发布会签到系统</a> <div id="sideNav" > <i class="fa fa-bars icon"></i> </div> </div> <ul class="nav navbar-top-links navbar-right"> <li class="dropdown"> <a class="dropdown-toggle" data-toggle="dropdown" href="#" aria-expanded="false"> <i class="fa fa-user fa-fw"></i> <i class="fa fa-caret-down"></i> </a> <ul class="dropdown-menu dropdown-user"> <li><a href="#"><i class="fa fa-user fa-fw"></i> User Profile</a> </li> <li><a href="#"><i class="fa fa-gear fa-fw"></i> Settings</a> </li> <li class="divider"></li> <li><a href="#"><i class="fa fa-sign-out fa-fw"></i> Logout</a> </li> </ul> <!-- /.dropdown-user --> </li> </ul> </nav> <!--/. NAV TOP --> <nav class="navbar-default navbar-side" role="navigation"> <div class="sidebar-collapse"> <ul class="nav" id="main-menu"> <li> <a class="active-menu" href="/sgin/events/"><i class="fa fa-dashboard"></i> 发布会</a> </li> <li> <a href="/sgin/guests/"><i class="fa fa-desktop"></i> 嘉宾</a> </li> </ul> </div> </nav> <!-- /. NAV SIDE --> <div id="page-wrapper"> <div class="header"> <div class="page-header"> {% block maintitle %}{% endblock %} </div> </div> <div id="page-inner" class="panel-body"> {% block content%} {% endblock %} <footer><p>Author:LiBoLiu. <a href="https://evcharging.auteltech.cn/dc-fast-charging-station/" target="_blank">云平台测试部</a></p> </footer> </div> <!-- /. PAGE INNER --> </div> <!-- /. PAGE WRAPPER --> </div> <!-- jQuery Js --> <script src="{% static 'assets/js/jquery-1.10.2.js' %}"></script> <!-- Bootstrap Js --> <script src="{% static 'assets/js/bootstrap.min.js' %}"></script> <!-- Custom Js --> <script src="{% static 'assets/js/custom-scripts.js' %}"></script> <script> $(document).ready( $('#main-menu>li>a').each(function (){ $(this).attr('class',''); //先取消选中 let current_href = window.location.pathname if(current_href === $(this).attr('href')){ $(this).attr('class','active-menu'); } }), ) </script> </body> </html>

{% extends 'base.html' %} {% block content %} <div class="panel panel-info"> <div class="panel-heading"> 新增发布会</div> <div class="panel-body"> <form method="post" action="/sgin/add_event/" enctype="application/x-www-form-urlencoded"> {% csrf_token %} <div class="form-group"> <label class="col-sm-2 control-label">名称</label> <div class="col-sm-10"> <input class="form-control" type="text" name="name" required> </div> </div> <div class="form-group"> <label class="col-sm-2 control-label">地址</label> <div class="col-sm-10"> <input class="form-control" type="text" name="address" required> </div> </div> <div class="form-group"> <label class="col-sm-2 control-label">人数</label> <div class="col-sm-10"> <input class="form-control" type="text" name="limits" required> </div> </div> <div class="form-group"> <label class="col-sm-2 control-label">状态</label> <div class="col-sm-10"> <input class="form-control" type="checkbox" name="status" checked> {# type="checkbox" 勾选框 #} </div> </div> <div class="form-group"> <label class="col-sm-2 control-label">时间</label> <div class="col-sm-10"> <input class="form-control" type="datetime-local" name="start_time" required> {# type="datetime-local" 设置时间控件 #} </div> </div> <button class="btn btn-block btn-primary" type="submit">保存</button> </form> {% if error %} <p class="danger"> {{ error }}</p> {% endif %} <p><a href="/sgin/events" class="btn btn-info">返回列表</a></p> </div> </div> {% endblock %}

{% extends 'base.html' %} {#详情页面模板一:简单的展示 #} {# 发布会详情页也继承base.html 页面 ,然后修改插槽数据#} {#{% block title%}发布会详情页{% endblock %}#} {#{% block content%}#} {#<h1>发布会详情页</h1>#} {# <p><a href="/sgin/events/" class="btn btn-info">返回发布会列表</a></p>#} {# 返回按钮,返回到返回发布会列表页面 #} {# 写个a元素放到p元素里是为了和发布会详情页间隔一行显示,分行 #} {#{% endblock %}#} {# 详情页模板二: #} {% block title%}发布会详情页{% endblock %} {# 标签 block...endblock: 父模板中的预留区域,该区域留给子模板填充差异性的内容,不同预留区域名字不能相同 #} {% block content%} <div class="panel panel-info"> {# 最外层的div就是一个样式, #} <div class="panel-heading"> 发布会详情页 </div> <div class="panel-body"> <p>发布会名称:{{ event.name }}</p> <p>发布会时间:{{ event.start_time }}</p> <p>发布会地址:{{ event.address }}</p> <form class="form-horizontal" action="/sgin/do_sgin/{{ event.id }}" method="post" enctype="application/x-www-form-urlencoded"> <input type="text" class="form-control" placeholder="请输入手机号" name="phone"> <button type="submit" class="btn btn-block btn-info" >签到</button> {%csrf_token%} </form> {# 表单传递用户签到信息#} {# 需要穿一个event.id,也就是发布会的id #} {# <span class="btn-danger btn"></span>#} {# 显示错误的区域#} {% if error %} {# 如果有error才显示信息块#} <span class="btn-danger">{{ error }}</span> {% endif %} <p><a href="/sgin/events" class="btn btn-info">返回发布会列表</a></p> </div> </div> {% endblock %}

{% extends "base.html" %} {# extends:继承base.html父(主)页面,修改想要插槽的内容就行了 #} {# 那么 base.html 页面就要预留出插槽, 想去查看base.html {% block content%} {% endblock %} #} {#一:extends "base.html" 继承父页面 #} {#二: #} {% block title%}发布会管理{% endblock %} {# 插槽插入标题 #} {% block maintitle%} <a href="/sgin/add_event_page/" class="btn btn-info navbar-right">新增发布会</a> {# 超链接做成button的样式#} {% endblock %} {# 插槽一个添加嘉宾按钮 #} {% block content%} {# 插槽插入数据内容 #} <ul class="list-group"> {% for event in event_list %} <li class="list-group-item text-center"> <a href="/sgin/events/{{ event.id }}">{{ event }}</a> {# 这里写event或者 event.name都可以,没有name默认调用event的__str__函数 #} {# 多个选项增加超链接 #} {# 开头记得写 / href="/events/detail" 不写就点击超链接的时候地址追加到浏览器当前的地址,追加模式,我们需要的是覆盖模式所以前面加 / #} </li> {% endfor %} </ul> {% endblock %}

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>发布会</title> </head> <body> <h1 style="color: red">这里是发布会</h1> {# <h3>{{event_list}}</h3> 这样写直接是传进去整个列表字符串数据了 #} <ul> {% for event in event_list %} <li>{{ event }}</li> {# 这里是注释,Django模板的注释 这里是循环体,Django的循环模板 前面写表达式,遍历的变量event#} {% endfor %} </ul> <h3> {# {% if 'python发布会' in event_list%} #} {% if event_list|length == 3 %} {# 过滤器写法:变量|过滤器 Django提供了很多内置的过滤器,我们也可以自己实现过滤器 #} <span>有三个发布会</span> {% endif %} </h3> </body> </html>

{% extends 'base.html' %} {% block content %} <div class="panel panel-info"> <div class="panel-heading"> 新增嘉宾 </div> <div class="panel-body"> <form method="post" action="/sgin/add_guest/" enctype="application/x-www-form-urlencoded"> {% csrf_token %} <div class="form-group"> <label class="col-sm-2 control-label">名称</label> <div class="col-sm-10"> <input class="form-control" type="text" name="name" required> </div> </div> <div class="form-group"> <label class="col-sm-2 control-label">手机号</label> <div class="col-sm-10"> <input class="form-control" type="text" name="phone" required> </div> </div> <div class="form-group"> <label class="col-sm-2 control-label">邮箱</label> <div class="col-sm-10"> <input class="form-control" type="text" name="email" required> </div> </div> <div class="form-group"> <label class="col-sm-2 control-label">发布会</label> <div class="col-sm-10"> <select class="form-control" name="event_ids" multiple> {# multiple:不加这个是单选框,加了是多选框 会把选中的根据 event_ids:xxx, event_ids:xxx ... post提交 #} {% for event in events %} <option value={{ event.id }}>{{ event.name }}</option> {% endfor %} </select> </div> </div> <button class="btn btn-block btn-primary" type="submit">保存</button> </form> {% if error %} <p class="danger"> {{ error }}</p> {% endif %} <p><a href="/sgin/guests" class="btn btn-info">返回列表</a></p> </div> </div> {% endblock %}

{% extends 'base.html' %} {% block title%}嘉宾详情页{% endblock %} {% block content%} <div class="panel panel-info"> <div class="panel-heading"> 嘉宾详情页 </div> <div class="panel-body"> <p>名称:{{ guest.name }}</p> <p>手机号:{{ guest.phone }}</p> <p>邮箱:{{ guest.email }}</p> {# <p>加入时间:{{ guest.evjoin_time }}</p>#} {# <p>关联发布会:{{ guest.event.name }}</p> 外键关联可以这样写 #} <p>关联发布会: {% for event in guest.event.all %} <a href="/sgin/events/{{ event.id }}">{{ event.name }}</a> {% endfor %} </p> {# <p>关联发布会:{{ guest.event.all }}</p>#} {# 多对多关联的话,只能如下操作#} {# guest.event.all() 返回一个QuerySet的数据类型,这个集合里包含 guest 所关联的所有 event发布会#} {# guest.event对应的也是一个多对多的管理器,可以使用all,filter这些方法,event指向的不是一个数据对象,指向的是一个多对多的列表#} {# guest.event是当前guest对象映射的数据,这些数据通过多对多管理器管理的,使用all,get,filter等查询方法#} <p><a href="/sgin/guests/" class="btn btn-info">返回嘉宾列表</a></p> </div> </div> {% endblock %}

{% extends "base.html" %} {% block title%}嘉宾管理{% endblock %} {% block maintitle %}<a href="/sgin/add_guest_page" class="btn btn-info navbar-right">添加嘉宾</a>{% endblock %} {% block content%} <ul class="list-group"> {% for guest in guest_list %} <li class="list-group-item text-center"> <a href="/sgin/guests/{{ guest.id }}">{{ guest }}</a> </li> {% endfor %} </ul> {% endblock %}

{% extends 'base.html' %}
{% block maintitle %}收到手机号: {{ phone }}, 签到成功!{% endblock %}

NONE

from django.contrib import admin from sgin import models # Register your models here. # 注册模型,注册Event模型,数据操作模型 admin.site.register(models.Event) admin.site.register(models.Guest)

from django.apps import AppConfig class SginConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'sgin'

from django.db import models # Create your models here. # 发布会模型类:继承Django模型基类,需要调用里面的方法,继承以后Django给模型初始化的时候需要将模型字段同步到表里面,需要调用一些基类的方法 class Event(models.Model): name = models.CharField(max_length=256, null=False) # 发布会名称:字符串 最大长度256 非空 # CharField:类,表示字段类型是字符串,数据库字段类型 字符串,数字,整型,浮点型,布尔类型,时间格式 # null=False:设置非空,默认就是非空类型,True为允许为空 # max_length:设置最大长度,系统的一个保护,节省空间 address = models.CharField(max_length=256, null=False) # 发布会地点:字符串 最大长度256 非空 limits = models.IntegerField(default=100) # 发布会人数:整型 默认为100 status = models.BooleanField(default=True) # 发布会状态:布尔类型,默认为True start_time = models.DateTimeField(null=True) # 开始时间:日期时间类型,python里的datetime.datetime的实例 # 这里时间允许为空,不传值 def __str__(self): """覆盖对象对外的字符串表现形式 默认字符串表现形式是:<Event: Event object (1)> 这种类型 这个动用数据库的表结构,没有修改数据库字段 """ return self.name """ ORM数据操作: 常用的数据类型: CharField:最常用的类型,字符串类型。必须接收一个max_length参数,表示字符串长度不能超过该值 BooleanField:布尔值类型。默认值是None DateTimeField:日期时间类型。Python的datetime.datetime的实例。datetime数据直接传参不需要转换,取值的时候也转成datetime的模式 IntegerField:整数类型,范围 - 2147483648 到 2147483647 的值在 Django 支持的所有数据库中都是安全的 上面的这些数据类型都在models模块下面 字段类型参考:https://docs.djangoproject.com/zh-hans/3.1/ref/models/fields/#field-types ORM支持主流的数据库 常用的字段选项: null :如果是 True, Django 将在数据库中存储空值为 NULL。默认为 False。 default :该字段的默认值。可以是一个值或者是个可调用的对象(函数),不能是一个可更改的对象(模型实例、list、set 等)。 unique :如果设置为 True,这个字段必须在整个表中保持值唯一。默认为False。若为True该字段可以成为一个,唯一索引 verbose_name :字段的一个人类可读名称,如果没有给定详细名称,Django 会使用字段的属性名自动创建,并将下划线转换为空格 primary_key:设置True,改字段设置为模型的主键,最多只能一个字段设置,如果模型中没有字段设置为主键,那么Django会自动帮我们创建 一个叫id的自增字段为主键 大多关系型数据库只能有一个主键 剩余通用字段参考:https://docs.djangoproject.com/zh-hans/3.1/ref/models/fields/#field-options ORM激活模型与数据迁移: 模型类似图纸,需要作用到数据库,当前使用Django自带的sqlite数据库和数据库驱动,sqlite文本数据库,不需要安装程序,有问题简单使用 模型放到数据库: 0:控制台到项目根目录djangosite(第一个djangosite文件) 激活虚拟环境:djenv\Scripts\activate.bat 激活后进入项目根目录djangosite:(djenv) C:\\Users\Autel\Desktop\course_python\djangosite> 1:python manage.py makemigrations :生成模型迁移文件,这里暂时不操作数据库,只是生成一个python模型文件 迁移后看应用程序 migrations 里面 ,里面有一个0001_initial.py 文件,里面有我们定义的模型字段内容 假设文件修改了,增加了数据库模型,敲了这个命令提升:no changes detected python manage.py makemigrations sgin 后面加一下应用的名称试一试 2:python manage.py migrate:同步到数据库 这个才操作数据库,迁移文件作用到数据库 3:sqlliteStudio工具可以操作查看sqllite数据库 可以看到数据库里有个表:sign_event sign:应用程序 event:模型的小写 Django自带命令行: 1:激活虚拟环境:djenv\Scripts\activate.bat 后 进入djangosite 目录 2 :python manage.py shell 打印:(InteractiveConsole)这就进入了命令行了 3:导入自定义模型Event 4:调用自定义模型里面的增删改查方法 这时候shell就在项目根目录下面 from sgin.models import Event Event.objects.all() Event:对应导入模型类 objects:增加,查询等操作需要用到模型管理器,通过objects属性调用模型管理器 models定义了一个objects指向模型管理器,模型管理器调用all,create等方法 ORM数据API增加操作: Model.objects.create(**kws) 增加 create返回一个数据对象 例子:event_obj = Event.objects.create(name="python测试开发", address="召唤师峡谷") event_obj是一个数据对象,可以调用属性,比如: event_obj.name 输出 "python测试开发" event_obj.address event_obj.limit 100 event_obj.不存在的属性 报错 ORM数据API查询操作: Model.objects.all() ---- 获取所有查询结果,返回QuerySet对象 类似 select * from table 返回一个 QuerySet 数据结构,类似python里的列表,这个 QuerySet还有一些方法可以链式调用,可以使用list转换城列表 Model.objects.filter(**kw) --- 条件过滤查询,返回QuerySet对象,不加参数效果和all()一样 类似select * from table where xxx Event.objects.filter(name="python测试开发") Model.objects.all().filter(name="python测试开发") 链式调用和 Event.objects.filter(name="python测试开发")使用一直 all()返回的是一个QuerySet对象,filter返回的也是一个QuerySet对象,只有返回的是QuerySet对象就可以调用这些自带方法 QuerySet对象就可以直接调用all方法 Model.objects.filter().filter() 这样调用也可以 前一个搜索结果的基础上再调用一次,链式调用,无限可以这样写下去 Model.objects.all().filter(name="python测试开发").values() 获取一个键值对,根据values获取 Event.objects.filter(name="python测试开发").values(): 获取内容: <QuerySet [{'id': 1, 'name': 'python测试开发', 'address': '召唤师峡谷', 'limits': 100, 'status': True, 'start_time': None}]> 返回的还是一个QuerySet对象,里面的内容还是一个list,只不过list里就一个元素内容,可以使用list转换 list(Event.objects.filter(name="python测试开发").values()) 返回:{'id': 1, 'name': 'python测试开发', 'address': '召唤师峡谷', 'limits': 100, 'status': True, 'start_time': None}] 开发一些接口,接口返回json格式,可以直接使用values() 获取到相应的数据对象,不需要转换成键值对模式 list(Event.objects.filter(name="python测试开发").values("name", "address")) values里面还可以传入字段只查询相应的数据:[{'name': 'python测试开发', 'address': '召唤师峡谷'}] Model.objects.values(**kw) 返回QuerySet对象,内容是数据键值对 Event.objects.values("name") 返回:<QuerySet [{'name': 'python测试开发'}]> 可以使用list直接转换成列表 Model.objects.get() --- 获取单个数据对象 #没有匹配结果或者匹配结果超过1个都会报错 Event.objects.all().get(name='python测试开发') 根据条件返回的是数据对象,只返回一个数据 数据库里创建三个数据: Event.objects.all() 返回:<QuerySet [<Event: Event object (1)>, <Event: Event object (2)>, <Event: Event object (3)>]> 表示返回3个数据 Event.objects.all().get(address="召唤师峡谷") 返回<Event: Event object (1)>,get一点返回单个模型属性对象 Event.objects.all().get() 报错: 如果返回的结果不等于一个抛出异常:sgin.models.Event.MultipleObjectsReturned: get() returned more than one Event -- it returned 3! 使用get的时候注意异常保护,可能结果不等于一个,可能报错 搜索结果不知道obj1和obj2的结果,Event模型类重构了__str__函数后 Event.objects.all(): 返回:<QuerySet [<Event: python测试开发>, <Event: JAVA前端开发>, <Event: C++前端开发>]> 返回的数据清晰很多 ORM数据API修改操作: 1:拿到要修改的数据对象:event_obj = Event.objects.all().first() event_obj输出:<Event: python测试开发> 2:event_obj.address = "诺克萨斯" 这样修改了后数据库里还没有修改,改的只是对象的属性,在内存里修改了,还没有同步到数据库里面 3:event_obj.save() 这样才会报错到数据库里面,类似数据库操作commit提交 save不仅仅修改,可以先create一个event对象,然后对象里塞值,再调用save方法,通过这种方式实现创建数据--没有create方便 千万不要用 Model.objects.update(**kws) 修改数据,会把所有数据记录都更改 ORM数据API删除操作: 数据对象调用delete方法,模型管理器没有这个delete方法,因为管理器不好设置全部的 删除针对单个数据,不好针对整个表 针对整个表:Event.objects 针对单个数据: event_obj = Event.objects.all().first() event_obj.delete和save event_obj.delete() 删除后返回:(1, {'sgin.Event': 1})表示删除成功,返回一个元组,元组里包含一个ID QuerySet是一个查询集,查询集里有一个元素,QuerySet是一个类列表的数据,可以使用下标: Event.objects.filter(name="python测试开发")[0] 返回:<Event: Event object (1)> == event_obj(Model.objects.create里创建的Event_obj) Event.objects.all()[0] == Event_obj(Model.objects.create里创建的Event_obj) Event.objects.all()[0].name ————>输出 'python测试开发' 通过下标获取 Event.objects.all().first() == Event.objects.all()[0] 模型类就是表结构,数据就是对象,通过对象的属性获取到数据 模型管理器使用的时候调用的是 Event 模型类,比如:Event.objects.all() 而不是对象,不需要对象实例化 比如:Event().objects.all() 这样就报错拉 python shell使用 quit()命令退出重新激活 """ # 嘉宾模型:嘉宾和发布会的关系,目前设定多对1的关系,嘉宾多,发布会1,一个嘉宾只能参加一个发布会,多个嘉宾可以去一个发布会 # 多表之间的关系:多对1 多对多 1个嘉宾针对多个发布会 目前假设一个嘉宾只能参加一个发布会,发布会对应多个嘉宾 class Guest(models.Model): # 关联发布会,外键字段定义在多方,多个嘉宾可以在一个发布会上,关联的是Event这个表对象的主键 # event = models.ForeignKey(Event, on_delete=models.CASCADE) # models.CASCADE:如果删除了发布会,那么关联的嘉宾也会被删除 # 模型关系升级,当前 ForeignKey 定义的是 多对1,升级成 多对多 event = models.ManyToManyField(Event, through='GuestEvent') # 如果自己定义了中间表需要手动指定中间表 GuestEvent,不指定中间表就会自动生成一个默认中间表 通过 through 参数指定 # 这里可以吧 GuestEvent 类定义在前面然后 through=GuestEvent,一般通过引号指定就好了 # ManyToManyField:多对多的模型关系,由 多对1改成多对多 # ManyToManyField不需要设置 on_delete了,一个发布会删了嘉宾还可以关联其他发布会,不需要处理 on_delete事件,一个指向关联的模型数据就行 name = models.CharField(max_length=64, unique=True) # 姓名唯一约束,最大长度64 phone = models.CharField(max_length=11, unique=True) # 手机号使用字符串,长度11并且唯一 email = models.EmailField() # 邮箱:使用Django的邮箱格式EmailField,少一些校验,存储的时候会自动校验 # 加入时候:自动获取创建嘉宾数据的时间 auto_now_add:创建的时候自动回去当前时间 # join_time = models.DateTimeField(auto_now_add=True) # # is_sgin = models.BooleanField(default=False) # 嘉宾的签到状态,默认没有签到 def __str__(self): return self.name """ 数据库表关联: 1:外键定义方式(多对1):外键一列存储的是另外一个表的id或者什么,外键这个字段通常定义在多方,外键通常关联的是目标模型的主键,ORM这里传一下目标模型的类就行 models.ForeignKey # 此外键定义在多的1方 models.ForeignKey(目标模型类, on_delete=models.CASCADE) on_delete:嘉宾关联了发布会,存在约束关系,删了嘉宾关联的发布会,发布会被其他嘉宾关联这,所以这里约束on_delete on_delete可选项: 当一个外键关联的对象被删除时,Django模仿ondelete参数定义的sql约束将要执行的动作 CASCADE:模拟sql语言中的ON DELETE CASCADE约束,将定义有外键的模型对象同时删除(删除了A发布会,关联了A发布会的所有嘉宾都会被删除) PROTECT:阻止上面的删除操作,但是弹出ProtectedError异常(如果其他嘉宾关联了A发布会,删除A发布会删除不了) SET_NULL:将外键字段设置为NULL,只有当字段设置了null=True时,方可使用该值(如果删除了发布会A,嘉宾A的外键字段设置为NULL) SET_DEFAULT:将外键字段设置为默认值,只有当字段设置了default参数时,方可使用(设置默认值,不常用) DO_NOTHING:什么也不做(A的主键id是99,a,b,c都指向99,A删除了a,b,c还是什么都不变) SET():自定义值,参数可以为常量或者回调函数的返回值 2:外键的定义方式(多对多):1个学生可以对应多门课程,1门课程也可以对应多个学生 models.ManyToManyField # 定义在任意1方,只能定义在1方,不能双方都定义 外键的关联查询api: 通过发布会查询嘉宾或者通过嘉宾查找发布会--关联查询 进入Django_shell测试api:python manage.py shell 1:正向查询:多方查询单方 模型对象.外键 guest1.event直接查询guest1关联的发布会数据 guest = Guest.objects.all().first() 表示一行数据对象 guest.event 表示找到的数据对象,找到的是一行数据对象,这是获取发布会的数据对象 guest.event.name 查找这个数据对象的属性,获取发布会的其他属性 2:反向查询:单方查询多方 发布会获取下面的多个嘉宾,一方查多方 模型对象.外键模型的小写_set 比如:event.guest_set 外键模型是Guest,小写就是guest event.guest_set:获取到的是一个多对多的管理器,类似Guest.objects,对象管理器,包含的发布会对应的嘉宾里面所有数据的管理器,调用对应的查询方法 event.guest_set.all() 可以调用管理器的方法all,get,first等获取数据的方法 event.guest_set.all() <QuerySet [<Guest: 刘立波>, <Guest: 林大头>]> event.guest_set.get(name="林大头") <Guest: 林大头> 多对1,很确定多方查询出来的是1个数据对象,1对多,1放查询出来的可能是1个也可能是多个 1:定义数据库模型 Guest Guest类 同步到数据库:模型放到数据库 python manage.py makemigrations:创建数据库同步文件 python manage.py migrate:同步数据库 进入admin和后台:http://127.0.0.1:8001/admin/ 需要现在sgin/admin文件注册一下Guest模型,否则看不到:admin.site.register(models.Guest) 2:编写嘉宾管理页面--列表系统中所有的嘉宾 """ # 定义一个中间表模型:Guest主动关联的Event,所有一般定义成 GuestEvent class GuestEvent(models.Model): # 必填字段:通过外键关联对应的数据,定义两个外键 event = models.ForeignKey(Event, on_delete=models.CASCADE) # 当关联的发布会或者嘉宾一个被删除,那么中间表的对应关系就不存在了,不显示 guest = models.ForeignKey(Guest, on_delete=models.CASCADE) # 选填字段 is_sgin = models.BooleanField(default=False) # 嘉宾的签到状态,默认没有签到 join_time = models.DateTimeField(auto_now_add=True) # 嘉宾针对发布会的加入时间,也应该写入中间表里 class Meta: # 元类,用于设置模型元信息:表名.....等 db_table = 'sgin_guest_event' # 可以和 Guest,Event默认多对多关系绑定后自动创建的表名相同 # 如果 存在 sgin_guest_event 这张表,表里有时间,python manage.py migrate 数据库同步的时候会报错的,无法覆盖 # 要么删除表sgin_guest_event再执行数据库数据库同步和迁移 # 方式二:不想删除同名sql文件的话,查看执行生成的迁移文件:migrations文件下 最新的 0006_alter_guest_event_alter_guestevent_table.py # dependencies:依赖 operations:修改的动作 # 0006文件的修改详情参考文件

from datetime import datetime from django.test import TestCase from sgin.models import Event, Guest # Create your tests here. # 发布会模型测试 class EventTestCase(TestCase): """测试类的名称没有要求,只要继承TestCase就可以了""" def setUp(self) -> None: # 箭头表示返回值的一个类型,python3.6新加的功能,表示这个 setUp 函数返回值是一个None # 如果这 return "" 返回一个str类型解释器会有错误提示,但是编译器不影响解读和执行,运行不影响,避免代码少些错误 # 准备发布会数据 Event.objects.create(name="测试训练营1", address="德玛西亚", limits=300, start_time=datetime.now()) Event.objects.create(name="测试训练营2", address="德玛西亚", limits=100, start_time=datetime.now()) def test_event_address(self): """测试查询功能,用例测试发布会的地址""" event1 = Event.objects.get(name="测试训练营1") event2 = Event.objects.get(name="测试训练营2") assert event1.address == "德玛西亚" assert event2.address == "德玛西亚" def test_event_limits(self): event1 = Event.objects.get(name="测试训练营1") event2 = Event.objects.get(name="测试训练营2") assert event1.limits == 300 assert event2.limits == 100 # 嘉宾测试 class GuestTestCase(TestCase): def setUp(self) -> None: # 准备发布会数据 event = Event.objects.create(name="测试训练营3", address="德玛西亚", limits=300, start_time=datetime.now()) Guest.objects.create(event=event, name="叶孤城", phone="19999999999", email="19999999999@qq.com") # 因为嘉宾对象需要外键关联发布会,所以这里需要传一个event发布会的数据对象,或者 event_id 两者都可以 def test_update(self): guest = Guest.objects.get(phone="19999999999") guest.name = "阳顶天" # guest.save() # 注释这个数据也会执行成功,数据库不保存数据,但是上面 guest.name = "阳顶天" 已经把值保存到对象里了, # 下面 assert guest.name == "阳顶天" 再从对象里取值直接是从内存里访问这个值的,没有访问数据库 ---这条用例有问题 # assert guest.name == "阳顶天" 不要这样校验,这样只是校验内存里的值,没有从数据库里查,需要再从数据库里再查一下 guest2 = Guest.objects.get(phone="19999999999") assert guest.name == guest2.name # 如果改成功了必然相等的,save注释就不会通过了 def test_delete(self): guest = Guest.objects.get(phone="19999999999") # 删除 guest.delete() # 删除数据,delete后是真的删除了,删除后去查下一下 guest_list = Guest.objects.all() assert guest not in guest_list # 断言不存在了 # all()返回一个QuerySet,QuerySet是一个类似列表类型的数据,判断数据在不在列表里使用 in或者not in

from django.contrib import admin from django.urls import path from sgin import views # 子路由列表 urlpatterns = [ path('events/', views.event), # path('events/detail/', views.events_detail), path('events/<int:event_id>', views.events_detail), # 匹配的是 events/1(n) 这里就类似动态的路由,把id传递到视图函数,根据id获取唯一的数据对象丢入模板,然后模板里再渲染 # 把后面这串 _id 传到视图函数里面,根据 _id 获取唯一得数据对象丢到模板,模板里面再渲染 path('guests/', views.guests), # 嘉宾管理 path('guests/<int:guest_id>', views.guest_detail), # 添加签到路由 path('do_sgin/<int:event_id>', views.do_sgin), path('sgin_success/<int:phone>', views.sgin_success_page), # 签到成功页 path('sgin_fail/<str:error>', views.sgin_fail_page), # 签到失败重向 path('add_event_page/', views.add_event_page), # 添加发布会表单页面 path('add_event/', views.add_event), # 添加发布会表单页面 path('add_guest/', views.add_guest), # 新增嘉宾相关 path('add_guest_page/', views.add_guest), ] """ events/detail/ 这个路由匹配 views.events_detail 这个视图函数 detail可以换成 _id view视图函数events_detail的入参_id要和这名字相同 path('events/<int: _id>' 这里获取到的各种_id会传参会给events_detail函数的_id """

from django.db import transaction from django.shortcuts import render, redirect from django.http import HttpResponse from django.views.decorators.csrf import csrf_exempt from sgin.models import Event, Guest, GuestEvent # Create your views here. def event(requests): # 从数据库获取实时的数据 event_list = Event.objects.all() # 这样写就可以,直接返回一个QuerySet list(Event.objects.all()):这样list转换写也可以 # QuerySet本身返回的就是一个可迭代对象,不使用list转换也可以,也可以for循环 # event_list = [f"<li>{event}</li>" for event in event_list] # return HttpResponse(event_list) # return HttpResponse(event_list):这个只是简单的返回字符串 # 返回一个模板内容:需要读取出来放到响应里 # 默认导入了render函数: # 第一个参数requests请求对象 # 第二个参数:模板名称,填写一个字符串, 直接填写模板文件的名称,会自动从 templates 文件导入 # 这样就能返回一段Html内容 return render(requests, "events.html", {"event_list": event_list}) # 发布会详情页视图 def events_detail(requests, event_id): # 获取真实数据传递给模板,从某处获取到当前页面的发布会数据,根据一个字段去获取相应的内容 try: event = Event.objects.get(id=event_id) # 数据对象,get这个经常可能找不到报错,加一下try except # 视图函数获取id,这里获取单个数据对象 # Event.objects.all() 这个获取的是一个查询集,查询集包含数据对象,单个数据对象丢给模板,html模板文件可以直接 event.name这样调用 except: # event = "" # 找不到就event置空,或者返回一个404页面 return render(requests, "404.html") # 找不到返回一个404页面 return render(requests, "event_detail.html", {"event": event}) """ 模板技术一:变量 将视图函数的变量返回到模板中,Django将其渲染 用法: 视图返回变量:{"模板中用到的变量名": 变量} 上面的例子将event_list做成变量传到模板里面,然后模板再去渲染 如上:render(requests, "events_old.html", {"event_list": event_list}) 将需要传入模板的值放到第三个参数,视图函数返回了,模板里需要填坑调用这个传参: 查看templates文件下的events.html模板文件:<h3>{{event_list}}</h3> 使用的是第三个参数的字典里的 key 参数 "event_list" 模板技术二:For循环控制器 作用: 将列表类型的变量挨个展现出来 用法: 视图view返回列表 修改下原视图函数的返回,之前是字符串,现在改变为列表并且去掉标签 <ul> {% for event in event_list %} <li>{{ event }}</li> {# 这里是注释,Django模板的注释 这里是循环体,Django的循环模板 前面写表达式,遍历的变量event#} {% endfor %} </ul> 模板技术三:if 控制器 选择性渲染那些元素 作用: 条件控制,可以根据条件选择渲染那些元素 用法: {% if "TFBOY" in events %} <h3>8个课程的发布会</h3> {% endif %} for 和 if 这种定义在 {% %} 中的,称之为模板标签 采用网上现成的网页模板来进行调试 """ # 嘉宾 def guests(requests): # 从数据库获取嘉宾数据 guest_list = Guest.objects.all() # 一次性获取全部的嘉宾数据 return render(requests, "guests.html", {"guest_list": guest_list}) # 返回模板内容 # 嘉宾详情 def guest_detail(requests, guest_id): # 获取单个嘉宾数据,和发布会数据类似,通过guest_id来获取,视图函数获取参数,通过路由指定传参来获取,查看url文件 try: guest = Guest.objects.get(id=guest_id) except: return render(requests, "404.html") # 找不到返回一个404页面 return render(requests, "guest_detail.html", {"guest": guest}) # 处理签到:老的签到函数 # @csrf_exempt # def do_sgin(requests, event_id): # event = Event.objects.get(id=event_id) # 1:拿到待签到的发布会 # # event = Event.objects.get(pk=event_id) # pk,主键缩写,这里id就是主键,这样写也没有问题,和id是一个意思,这里主键的名字就是id # # 2:拿到手机号,获取签到的嘉宾,根据手机号获取嘉宾,手机号就是索引,手机号是post传递的 # """ # 处理http请求的数据:http请求的数据在requests里都可以拿到 # post目前传递的表单数据 # requests的处理: # requests.method:判断请求方法,值是大写的POST,GET,PUT,DELEYE # requests.POST[key]或者requests.POST.get():取post表单数据 # requests.POST返回的是一个字典格式 # """ # if requests.method == "POST": # 判断一下请求方法如果是POST,并且是表单格式的数据(key:value这种格式) # phone = requests.POST['phone'] # 拿到手机号,判断手机号是否正确 # # # try: # # Guest.objects.get(phone=phone) # 根据手机号去查询,手机号是不是存在在数据库来判断手机号的正确性 # # except: # # return HttpResponse("手机号错误") # # res = Guest.objects.filter(phone=phone) # 这样使用filter也行,查询不到数据是一个空的列表 # if not res: # # 如果手机号不正确 # # return HttpResponse("手机号错误") # return render(requests, 'event_detail.html', {"error": "手机号错误", "event": event}) # # return redirect(f'/sgin/sgin_fail/{"手机号错误"}') # 重定向到某个页面 # # guest = res[0] # 这样手机肯定正确,拿到嘉宾guest # # 判断嘉宾是否是当前发布会的嘉宾 # if guest.event.id != event_id: # # return HttpResponse("非当前发布会嘉宾") # return render(requests, 'event_detail.html', {"error": "非当前发布会嘉宾", "event": event}) # # 是否已经签到,如果为True表示嘉宾已经签到 # if guest.is_sgin: # # return HttpResponse("已签到,请勿重复提交") # return render(requests, 'event_detail.html', {"error": "已签到,请勿重复提交", "event": event}) # # # 执行签到 # try: # guest.is_sgin = True # guest.save() # 修改数据报错 # except: # # return HttpResponse("签到失败,服务器异常") # 报错失败返回签到失败 # return render(requests, 'event_detail.html', {"error": "签到失败,服务器异常", "event": event}) # # # return HttpResponse(f"嘉宾签到成功,手机号:{phone}") # 嘉宾签到成功 # # return render(requests, 'sgin_success.html', {"phone": phone}) # return redirect(f'/sgin/sgin_success/{phone}') # 重定向到另外一个路由,由另外的视图函数去处理返回结果 # # 调用这个redirect会跳转到 /sgin/sgin_success/这个路由上 这样一直刷新页面都在 /sgin/sgin_success/ 这个路由上一直调用sgin_success_page # 处理签到:新的签到函数 @csrf_exempt def do_sgin(requests, event_id): event = Event.objects.get(id=event_id) # 1:拿到待签到的发布会 if requests.method == "POST": phone = requests.POST['phone'] res = Guest.objects.filter(phone=phone) if not res: return render(requests, 'event_detail.html', {"error": "手机号错误", "event": event}) guest = res[0] event_list = guest.event.all() # 获取当前嘉宾对应的发布会列表 event_list # 判断当前嘉宾是不是当前发布会绑定的嘉宾,也就是 event 是否在 event_list 里 if event not in event_list: # 判断关联的发布会列表中是否存在当前签到的发布会 return render(requests, 'event_detail.html', {"error": "非当前发布会嘉宾", "event": event}) # 把 is_sgin 签到标记移动到 中间关系表里 ,中间表增加一个字段 is_sgin # 先取出对应的中间模型:查询中间数据---由操作嘉宾变成操作中间模型 # guest.event.all() # 获取到当前嘉宾绑定的全部发布会 ge = GuestEvent.objects.get(guest=guest, event=event) # 找到嘉宾和想要签到的发布会的中间表 # 操作中间模型 if ge.is_sgin: return render(requests, 'event_detail.html', {"error": "已签到,请勿重复提交", "event": event}) try: # 执行签到 ge.is_sgin = True ge.save() except: return render(requests, 'event_detail.html', {"error": "签到失败,服务器异常", "event": event}) return redirect(f'/sgin/sgin_success/{phone}') def sgin_success_page(requests, phone): return render(requests, 'sgin_success.html', {"phone": phone}) def sgin_fail_page(requests, error): if error == "手机号错误": return render(requests, 'event_detail.html', {"error": "手机号错误", "event": event}) """ do_sgin:查看url文件里这个路由,这个路由应该是个后台路由不出现在前端页面上 do_sgin这个函数只处理请求并且返回正确的结果,不应该显示爱浏览器上面,如果停留在页面上再去刷新浏览器会提示是否继续提交数据 这样做不安全,把这个do_sgin视图函数处理完以后该调到那个页面就去哪个页面,而不是继续停留在当前url里面 请求转发:重定向的问题 想去见txt文档 这里web端请求页面,处理的post请求,成功返回新的页面,失败返回错误消息,但是还是以前的页面 """ def add_event_page(requests): return render(requests, 'event_add.html') def add_event(requests): """这个数据是POST方法传递的""" # 获取发布会数据 if requests.method == "POST": name = requests.POST['name'] address = requests.POST['address'] limits = requests.POST['limits'] start_time = requests.POST['start_time'] # status = requests.POST['status'] 可能没有传 status 这个字段取值报错,使用 status = requests.POST.get('status') # status = requests.POST.get('status', False) # get方法获取如果status不存在不会报错,取到的为空,可以指定取不到的默认值为False status = True if requests.POST.get('status', False) else False # 三元表达式 # 创建发布会 try: event = Event.objects.create(name=name, address=address, limits=limits, status=status, start_time=start_time) except Exception as e: return render(requests, 'event_add.html', {"error": repr(e)}) # 如果创建发布会报错了就在当前 add页面显示错误信息 # repr(e) 这样获取报错信息e的一个精简信息 # 如果没有报错那么就保存成功了,那么这时候重定向一下,返回发布会列表 return redirect('/sgin/events/') # redirect可以直接放视图函数,也可以放视图函数指定的路由,都可以 # 新增嘉宾 def add_guest(request): # 返回新增表单页面 if request.method == 'GET': event_list = Event.objects.all() return render(request, 'guest_add.html', {'events': event_list}) # 处理新增请求表单 if request.method == 'POST': name = request.POST['name'] # 姓名 phone = request.POST['phone'] # 手机号 email = request.POST['email'] # 邮箱 # 关联发布会 # event_id = request.POST['event_id'] # event_ids = request.POST['event_ids'] #这种方式只能获取到最后一个参数名为event_ids的值--错误的方式 # 而创建嘉宾的时候关联多个发布会传参是 event_ids:1, event_ids:2, event_ids:3 # request.POST['event_ids'] 只能获取到列表的其中一个值,最后一个参数名为event_ids的值,无法接收全部的数据 event_ids = request.POST.getlist('event_ids') # 获取值的列表,获取多个,参数名字都是一样 # 创建嘉宾,新增嘉宾 try: # 事务:创建嘉宾+关联发布会 with transaction.atomic(): guest = Guest.objects.create(name=name, phone=phone, email=email) # 创建嘉宾 # 需要再在中间表增加关联关系 event_list = [Event.objects.get(pk=event_id) for event_id in event_ids] # 根据发布会id获取 发布会数据对象 列表 # 存的是数据对象,不是id pk是主键的意思,根据主键获取 guest.event.add(*event_list) # 方式一:单纯的添加 # 因为Guest类创建的时候 :event = models.ManyToManyField(Event) 所以可以调用event属性+方法 # guest.events.set(event_list) # 方式二:有覆盖的作用,一般修改使用的比较多 # set不需要一个个传递,不需要解包,直接传 列表就行,set有覆盖的作用,add只是单存的添加 # 二选一就行 except Exception as e: return render(request, 'guest_add.html', {'error': repr(e)}) # 返回精简错误信息 # 保存成功-跳转到嘉宾列表页 return redirect('/sgin/guests/') """ 创建嘉宾数据时,不要传入event_ids,应该用数据对象进行关联,方法是: 1:数据对象.多方.add(多方数据对象1,多方数据对象2,多方数据对象3,.....) guest.events.add(*event_list) 2:数据对象.多方.set(多方数据对象列表 guest.events.set(event_list) """
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
· DeepSeek “源神”启动!「GitHub 热点速览」
· 上周热点回顾(2.17-2.23)
2022-01-13 python开发基础篇:二