Django学习之路-基础篇
Django 学习之路
- 一、Django项目创建
- 1.项目结构
- 1.1. settings.py 文件
- 1.2 URL
- 1.3 视图函数
- 1.4 路由配置-path
- 二、请求与响应
- 2.1.请求和响应
- 2.1.1 django中的请求
- 2.1.2 django中的响应对象
- 2.2 GET请求和POST请求
- 2.3 Django的设计模式(MTV)
- 2.4 模板层
- 2.5 URL反向解析
- 三、静态文件
- 四、应用与路由
- 五、ORM模型
- 5.1 数据库
- 5.2 模型类
- 5.3 ORM框架
- 数据库操作常见问题汇总
- 六、admin管理后台
- 6.1 admin配置步骤
- 6.2 注册自定义模型类
- 6.3 关系映射
- 七、会话保持
- 7.1 Cookies
- 7.2 session
- 7.3 缓存
- 八、中间件
- 8.1 中间件的定义
- 8.2 中间件使用
- 8.3 CSRF - 跨站伪造请求攻击
- 九、网页分页功能
- 十、生成CSV文件
- 十一、用户认证系统
- 十二、文件上传功能
- 十三、发送邮件
- 十四、项目部署
一、Django项目创建
1.项目结构
1.1. settings.py 文件
"""
Django settings for mydemo project.Generated by 'django-admin startproject' using Django 3.1.7.《django版本》For more information on this file, see
https://docs.djangoproject.com/en/3.1/topics/settings/For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.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/3.1/howto/deployment/checklist/# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'jb^(bip2g_@%r*5ni-8@#yz%*crzw7sqpq_qcbhw48lgk+ctxo'# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
"""
调试模式开关,默认为True(调试模式)
为True时:1.检测代码改动后立刻重启服务;2.显示报错页面
为False时(正式启动模式/上线模式):项目上线时一定要更改为False
"""ALLOWED_HOSTS = []
"""
允许的域名,只有列表中添加的域名可以访问服务器,默认会允许127.0.0.1或者localhost访问
设置为"*":表示允许所有域名访问
设置当前局域网内域名可进行访问:
1.项目启动时设置:python manage.py runserver 0.0.0.0:8000
2.添加局域网IP
"""# Application definitionINSTALLED_APPS = ['django.contrib.admin','django.contrib.auth','django.contrib.contenttypes','django.contrib.sessions','django.contrib.messages','django.contrib.staticfiles',
]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 = 'mydemo.urls'
"""
项目主路由位置,默认为项目文件夹下的urls.py
"""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 = 'mydemo.wsgi.application'# Database
# https://docs.djangoproject.com/en/3.1/ref/settings/#databasesDATABASES = {'default': {'ENGINE': 'django.db.backends.sqlite3','NAME': BASE_DIR / 'db.sqlite3',}
}# Password validation
# https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validatorsAUTH_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/3.1/topics/i18n/LANGUAGE_CODE = 'en-us'
"""
主页语言配置,可设置为中文
LANGUAGE_CODE = 'zh-hans'
"""
TIME_ZONE = 'UTC'
"""
时区设置,主要影响数据库时间显示
TIME_ZONE = 'Asia/shanghai'
"""
USE_I18N = TrueUSE_L10N = TrueUSE_TZ = True# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.1/howto/static-files/STATIC_URL = '/static/'
"""
静态文件路由
"""
1.2 URL
URL 结构:protocol://hostname[:port]/path[?query][#fragment]
- protocol(协议):
- http:通过http访问该资源,格式:http://
- https:通过安全的https:访问该资源,格式:https://
- file:通过file访问本地的资源,格式:file:///
- hostname(主机名/域名):
- 是指存放资源的服务器的域名系统(DNS)主机名、域名或IP地址
- port(端口):
- 可选,省略时使用方案的默认端口
- 各种传输协议都有默认的端口号http:默认是80,https:默认是443
- path(路由地址):
- 由零或多个“/”符号隔开的字符串,一般用来表示主机上的一个目录或文件地址,路由地址决定了服务器端如何处理这个请求
- query(查询字符串):
- 可选,以“?”开头,用于给动态网页传参,可传递多个参数用“&”隔开,参数名和参数值用等号隔开
key=value
- 可选,以“?”开头,用于给动态网页传参,可传递多个参数用“&”隔开,参数名和参数值用等号隔开
- fragment(信息片段):
- 字符串,以“#”开头,用于指定网络资源中的片段,类似书签,下次访问时带上fragment可直接定位到该片段
- protocol(协议):
Django如何处理URL请求
- 根据浏览器传过来的URL,django从配置文件中根据ROOT_URLCONF找到主路由文件;默认情况下该路由文件位于同名目录下的urls.py中
- django加载主路由中的urlpattern变量,urlpattern是一个包含很多路由的列表
- 从上往下依次匹配urlpattern中的path,匹配到第一个满足的path即中断后续匹配
- 匹配成功,调用对应的视图函数处理请求,返回对应的响应
- 匹配失败,返回404响应
1.3 视图函数
定义:用于接收浏览器请求( HttpRequest对象),并通过HttpResponse对象返回响应的函数
语法:return必须是HttpResponse对象
def demoview(request[,其他参数]):return HttpResponse('需要返回的数据')
示例:
在项目同名目录下创建views.pyfrom django.http import HttpResponse def page1_view(requet):html = "<h1>hallo world</h1>"return HttpResponse(html)
添加路由url.py
from django.contrib import admin from django.urls import path from . import views urlpatterns = [path('admin/', admin.site.urls),path('page1/', views.page1_view), ]
1.4 路由配置-path
- path()函数
- 导入
from django.urls import path
- 语法
path(route,views,name=None)
- 参数:
- route:字符串类型,匹配的请求路径
- views:指定路径所对应的视图处理函数的名称
- name:为地址起别名,在模板中进行地址反向解析时使用
- path转换器
- 语法:<转换器类型:自定义名>
- 作用:若转换器类型匹配到对应类型的数据,则将数据按照关键字传参的方式传递给视图函数
- 例:
path('page/<int:page>',views.xxx)
,如果匹配到page路径后面时int类型,则会将接收到的路径中的数据赋值给变量page,然后以关键值传参的方式传递到视图中 转换器类型 作用 样例 str 匹配除了‘/’之外的非空字符串 ”v1/users/<str:username>“匹配/v1/users/china,匹配结果为:username=china
int 匹配0和任何正整数,返回一个int ”v1/users/<int:num>“匹配/v1/users/35,匹配结果为:num=35
slug 匹配任意有ASCII字母或数字以及连字符和下划线组成的短标签 ”v1/users/<slug:sl>“匹配/v1/users/this_is_django,匹配结果为:sl=this_is_django
path 匹配非空字段,包括路径分隔符”/“ ”v1/users/<path:ph>“匹配/v1/users/goods/a/b/c,匹配结果为:ph=goods/a/b/c
- re_path转换器
- 语法:
re_path(reg,view,name=xxx)
- 正则表达式必须为命名分组模式(
?P<name>pattern
;匹配成功后用关键字传参的方式传递给视图函数
- 正则表达式必须为命名分组模式(
- 作用:使用正则进行精确匹配
- 例:
url(r'^weather/(?P<city>[a-z]+)/(?P<year>\d{4})/$', views.weather),
- 语法:
二、请求与响应
2.1.请求和响应
2.1.1 django中的请求
- 视图函数中的第一个参数即为HttpRequest对象
- django接受到http协议的请求后,会根据请求数据报文创建HttpRequest对象
- HttpRequest对象通过属性,描述了请求的所有相关信息
HttpRequest属性:
属性名 说明 path_info URL字符串 method 字符串,表示Http请求方法:GET、POST、PUT等 GET QueryDict查询字典的对象,包含get请求方式的所有数据 POST QueryDict查询字典的对象,包含post请求方式的所有数据 FILES 类似于字典的对象,包含所有的上传文件信息 COOKIES python字典,包含所有的cookie,键和值都为字符串 session 类似于字典对象,表示当前的会话 body 字符串请求体的内容 scheme 请求协议(http或者https) request.get_full_path() 请求的完整路径,会将查询字符串一并取出 request.META 请求中的元数据(消息头) requset.META[‘REMOTE_ADDR’] 客户端IP地址
2.1.2 django中的响应对象
- 构造函数:
- HttpResponse(content=响应体,content_type=响应体数据类型,status=状态码)
- 作用:
- 向客户端浏览器返回响应,同时携带响应体类容
- 参数:
- content: 表示返回的内容
- status_code: 返回的HTTP响应状态码(默认为200)
- content_type: 指定返回数据的MIME类型(默认为text/html),浏览器会根据这个属性来显示数据
- 常见content_type:
- ‘text/html’: 默认的,HTML文件
- ‘text/plain’: 纯文本
- ‘text/css’: css文件
- ‘text/javascript’: js文件
- ‘multipart/form-data’: 文件提交
- ‘application/json’: json传输
- ‘application/xml’: xml文件
- 常见content_type:
- HttpResponse子类
类型 作用 状态码 HttpResponseRedirect 重定向 302 HttpResponseNotModified 未修改 304 HttpResponseBadRequest 错误请求 400 HttpResponseNotFound 没有对应的资源 404 HttpResponseForbidden 请求被禁止 403 HttpResponseServerError 服务器错误 500
2.2 GET请求和POST请求
- 定义:
- 无论是GET还是POST,统一都由视图函数接收,通过判断request.method区分具体的请求动作
- 样例
if request.method == 'GET':处理get请求时的业务逻辑 elif request.method == 'POST':处理POST请求时的业务逻辑 else:处理其他请求业务的逻辑
- GET处理
- GET请求动作一般用于向服务器获取数据
- 能够产生GET请求的场景
- 浏览器地址栏输入URL回车后
<a href = "地址?参数=值&参数=值">
- form表单中的method为get
- GET请求中,如果有数据需要传递给服务器,通常会用查询字符串(Query String)传递,注意不要传递敏感数据
- URL格式:XXX?参数名1=值&参数名2=值(如:http://127.0.0.1:8000?a=100&b=200)
- 服务器端接收参数:获取客户端请求GET提交的数据方法示例
request.GET['参数名'] #得到该参数名的值 request.GET.get('参数名',’默认值‘) #得到该参数名的值,如果该参数名不存在则返回默认值 request.GET.getlist(’参数名‘) #如果该参数名存在多个返回值,则用getlist来接收```
- POST 处理
- POST请求动作一般用于向服务器提交大量/隐私数据
- 客户端通过表单等POST请求将数据传递给服务器端;如:
<form method='post' action="/login"> #action指明post请求发给哪个路由姓名:<input type="text" name="username"><input type='submit' value='登陆'> </form>
- 服务器端接收参数:通过request.method来判断是否为post请求
- 使用post方式接收客户端数据
request.POST['参数名'] #得到该参数名的值 request.POST.get('参数名',’默认值‘) #得到该参数名的值,如果该参数名不存在则返回默认值 request.POST.getlist(’参数名‘) #如果该参数名存在多个返回值,则用getlist来接收
- 注意:发送POST请求时,django需要取消csrf验证,否则django会拒绝客户端发来的POST请求,报403响应
- 取消csrf验证:注释掉settings.py文件中的MIDDLEWARE中的csrfviewsmiddleware的中间件
2.3 Django的设计模式(MTV)
- 设计模式:MVC和MTV
- MVC(model-view-controller)模型-视图-控制器模式
- M 模型层,主要用于对数据库层的封装
- V 视图层,用于向用户展示结果
- C 控制层,用于处理请求、获取数据、返回结果
- 优点:降低模块间的耦合度
- MTV (model-template-view) 模型-模版-视图模式
- M 模型层,负责与数据库交互
- T 模板层,负责呈现内容到浏览器(html)
- V 视图层,负责接收请求、获取数据、返回结果
- 优点:降低模块间的耦合度
- MVC(model-view-controller)模型-视图-控制器模式
2.4 模板层
模板定义:
- 模板是可以根据字典数据动态变化的HTML网页
- 模板可以根据视图中传递的字典数据动态生成相应的HTML网页
模板配置
- 创建模板文件夹<项目名>/templates
- 在settings.py中TEMPLATES配置项
- BACKEND:指定模板的引擎
- DIRS:模板的搜索目录,可以是一个或多个
- APP_DIRS:是否要在子应用中的templates文件夹中搜索模板文件
- OPTIONS:有关模板的选项
- 配置中需修改的部分
- 设置DIRS - ‘DIRS’:[os.path.join(BASE_DIR,‘templates’)],
模板的加载方式
- 方案一:通过loader获取模板,通过HttpResponse进行响应,在视图函数中进行如下配置
from django.template import loader t = loader.get_template("模板文件名") #通过loader加载模板,得到一个loader对象 html = t.render(字典数据) #将t(loader对象)转换成HTML字符串 return HttpResponse(html) #用响应对象的方式将转换的字符串内容返回给浏览器
- 方案2:使用render()直接加载并响应模板,在函数视图中进行如下配置
from django.shortcuts import render return render(request,'模板文件名',字典数据)
- 方案一:通过loader获取模板,通过HttpResponse进行响应,在视图函数中进行如下配置
视图层与模板层之间的交互
- 视图函数中可以将python变量封装到字典中传递到模板;样例:
def xxx_view(request):dic = {"变量1":"值1","变量2":"值2",}return render(request,'xxx.html',dic)
- 在模板中,我们可以使用 {{变量名}} 的语法调用视图传进来的变量
- 视图函数中可以将python变量封装到字典中传递到模板;样例:
模板层-变量
- 模板层可接收的数据类型
类型名 注释 str 字符串 int 整型 list 列表 tuple 元组 dict 字典 func 方法 obj 类实例化对象 - 在模板中使用变量语法
- {{ 变量名 }}
- {{ 变量名.index }} #传入的变量为列表、元组等,可用.index方法取出其中的元素
- {{ 变量名.key }} #传入的变量为字典,可用.key方法获取key所对应的值
- {{ 对象.方法 }} #调用对象的方法
- {{ 函数名 }} #调用函数
- 模板标签
作用:将一些服务器端的功能嵌入到模板中,例如流程控制等
语法:
{% 标签 %} ………… {% 结束标签 %} #django中大部分标签都需要使用结束标签封口
IF标签
- 语法
{% if 条件表达式1 %} …… {% elif 条件表达式2 %} …… {% else %} …… {% endif %} #必须使用endif标签进行结束
- 注意:
- if条件表达式例可以用的运算符==,!=,<,>,<=,>=,in.not in,is,is not,not,and,or;
- 在if标记中使用实际括号是无效的语法,如果需要他们指示优先级,则应使用嵌套的if标记
- if语句中,运算符两侧不能紧挨变量或常量,必须用空格隔开
- 关于if标签的语法参考官方文档
- 样例
<form action='/text' method="post"><input type="text" name="x" value='{{ x }}'><select name="op"><option value="add" {%if op == 'add' %}selected{%endif%}> +加</option><option value="sub" {%if op == 'sub' %}selected{%endif%}> -减</option><option value="mul" {%if op == 'mul' %}selected{%endif%}> *乘</option><option value="div" {%if op == 'div' %}selected{%endif%}> /除</option></select><input type="text" name="y" value='{{ y }}'> = <span>{{ result }}</span><div><input type="submit" value="开始计算"></div> </form>
- 语法
for 标签
- 语法:
{% for 变量 in 可迭代对象 %} …… 循环语句 {% empty %} …… 可迭代对象为空时显示的语句 {% endfor %}
- 可迭代对象的传递,从视图函数以字典形式传递给模板文件,需要保证模板文件中的可迭代对象名与视图函数中定义的键名相同
<h3>{% for i in x %}{{ forloop.counter }}this is in for {{i}} {%endfor%}</h3> # x由视图函数传入
- 关于for标签的语法参考官方文档
- 语法:
内置变量 - forloop
变量 描述 forloop.counter 返回当前循环的索引(从1开始索引) forloop.counter0 返回当前循环的索引(从0开始索引) forloop.revcounter 返回当前循环的索引(从1开始索引counter值的倒序) forloop.revcounter0 返回当前循环的索引(从0开始索引counter值的倒序) forloop.first 如果当前循环是第一次循环,则返回True forloop.last 如果当前循环是最后一次循环,则返回True forloop.parentloop 如果为嵌套循环,parentloop则表示外层循环
- 模板过滤器
定义:在变量输出时,对变量的值进行处理
作用:可以通过使用过滤器来改变变量的输出显示
语法:{{变量|过滤器1:‘参数值1’ | 过滤器2:‘参数值2’}}
关于过滤器的语法参考官方文档
常用过滤器:
过滤器 说明 lower 将字符串转换为全部小写 upper 将字符串转换为全部大写 safe 禁用转义,告诉模板这个变量是安全的,可以解释执行 add:“n” 将value的值增加n default:“默认值” 变量不存在时,返回默认值 truncatechars:‘n’ 如果字符串的字符多余指定的字符数量,那么会被截断,截断的字符串将以可翻译的省略号序列("…")结尾
- 模板的继承
- 定义:模板的继承可以使父模板的内容重用,子模板直接继承父模板的全部内容并可以覆盖父模板中相应的块
- 语法 - 父模板中:
- 定义父模板中的块block标签
- 标识出哪些在子模块中是允许被修改的(block标记的继承时都是可以进行修改的)
- block标签:在父模板中定义,可以在子模板中覆盖
- 语法 - 子模板中:
- 继承模板extends标签(写在模板文件的第一行)
- 例如
{% extends 'base.html '%}
#继承父模板base.html
- 例如
- 子模板重写父模板中的内容块
{% block block_name %} 子模板块用来覆盖父模板中 block_name 块的内容 {% endblock block_name %} #block_name可省略
- 继承模板extends标签(写在模板文件的第一行)
- 重写的覆盖规则:
- 不重写,将按照父模板的效果显示
- 重写,则按照重写效果显示
- 注意:模板继承时,服务器端的动态内容无法继承,例如:视图给父模板传递的变量,在子模板中无法拿到
- 例:父模板中
{% block block_name %} <h4>this is in parent</h4> {% endblock %}
子模板中
{% extends 'test_html.html' %} {% block block_name %} <h3> this is in base.html </h3> {% endblock %}
- 关于过滤器的语法参考官方文档
2.5 URL反向解析
- URL:
- 代码中常出现URL的位置:
- 模板【HTML中】:
<a href='url'>超链接</a>
#点击后页面跳转至url<form action='URL' method='post'>
#form表单中的数据用post方法提交值URL
- 视图函数中 - 302跳转 HttpResponseRedirect(‘url’) #将用户地址栏中的地址跳转到URL
- 模板【HTML中】:
- 代码中URL书写规范:
- 绝对地址:http://127.0.0.1:8000/page1 (将协议、域名、端口、path全部写出)
- 相对地址:
- ‘/page/1’ : '/'开头的相对地址,浏览器会把当前地址栏里的协议、IP、端口加上这个path,作为最终访问地址。如:http://127.0.0.1:8000 + /page/1
- ‘page/1’ : 没有’/‘开头的相对地址,浏览器会根据当前URL的最后一个’/'之前的内容加上这个path最为最终访问地址。如:http://127.0.0.1:8000/topic/detail,加上相对地址后的访问地址为:http://127.0.0.1:8000/topic/page/1
- URL反向解析
- 定义:URL反向解析是指在视图或模板中,用path定义的名称来动态查找或计算出相应的路由
- 语法:
- path(route,views,name=‘别名’)
- 例:path(‘page’,views.page_view,name = ‘page_url’)
- 根据path中的name关键字传参给URL确定了唯一的名字,在模板或视图中,可以通过这个名字反向推断出此URL信息
- 模板中 - 通过URL标签实现地址的反向解析
{% url '别名' %}
{% url '别名' '参数值1' '参数值2' %}
,参数值1:向路由中传入的参数- 例
{% url 'page1' '100' %}
#解析name=page1的路由,并传入参数100
{% url 'page2' key1='1' key2='2' %}
#解析name=page2的路由,并关键字传参key1,key2 注意 路由解析时传递的参数均为字符串方式传入
- 在视图函数中 - 通过调用django中的reverse方法进行反向解析
from django.urls import reverse reverse('别名', args=[ ], kwargs={ }) #例 : reverse('page1',args=[100]) #解析name=page1的路由,并传入参数100 reverse('page2',kwargs={key1=1,key2=2})
- 代码中常出现URL的位置:
三、静态文件
- 定义:项目中的css、图片、js、音频、视频等
- 静态文件配置
- 静态文件使用 - 配置(settings.py):
- 配置静态文件的访问路径,该配置创建项目后默认存在
- 通过那个URL地址能找到静态文件
- STATIC_URL = ‘/static/’
- 说明:指定访问静态文件时是需要通过/static/xxx或http://127.0.0.1:8000/static/xxx,其中xxx表示具体的静态资源位置
- 配置静态文件的存储路径 STATICFILES_DIRS,STATICFILES_DIRS保存的是静态文件在服务器中的存储位置
#file:settings.py STATICFILES_DIRS = ( os.path.join(BASE_DIR,'static'), )
- 静态文件访问
- 通过模板访问静态文件,例:
<img src="http://127.0.0.1:8000/static/image1.jpg" width="200" height="200"> #绝对路径访问 <img src="static/image2.jpg" width="200" height="200" > #相对路径访问
- 通过{% static %}标签访问静态文件 推荐使用此方法进行静态文件加载
- 加载static - {% load static %}
- 使用静态资源 - {% static ‘静态资源路径’ %}
- 样例:
<img src='{% static 'images/lena.jpg' %}' >
访问static文件夹下images文件夹下的lena.jpg
- 配置静态文件的访问路径,该配置创建项目后默认存在
- 静态文件使用 - 配置(settings.py):
四、应用与路由
- 应用:
- 定义:在django项目中是一个独立的业务模块,可以包含自己的路由、视图、模板、模型
- 创建应用:
- 用manage.py中的子命令startapp创建应用文件夹
python manage.py startapp app_name
#子应用名不能与python关键字重名
- 在settings.py文件中的INSTALLED_APPS列表中配置安装此应用
- 样例:
INSTALLED_APPS = [ #…… 'app1_name', #应用名 'app2_name', ]
- 用manage.py中的子命令startapp创建应用文件夹
- 分布式路由
django中主路由配置文件(urls.py)可以不处理用户的具体路由,主路由配置文件可以做请求的分发(分布式请求处理)。具体的请求可由各自的应用来进行处理,- 分布式路由配置:
- 主路由中调用include函数
- 语法:include(‘app_name.url模块名’)
- 作用:用于将当前路由转到各个应用的路由配置文件的urlpatterns进行分布式处理
- 以http://127.0.0.1:8000/users/index为例
from django.urls import psth,include from . import viewsurlpatterns = [ path('admin/',admin.site.urls), path('users/',include('users.urls')) #分发至子应用users中的urls.py ]
- 应用下配置urls.py
- 应用文件夹下手动创建urls.py,内容结构与主路由完全一样
from django.urls import path from . import views urlpatterns = [ #http://127.0.0.1:8000/users/index path('index',views.index_view) ]
- 应用文件夹下手动创建urls.py,内容结构与主路由完全一样
- 主路由中调用include函数
- 分布式路由配置:
- 应用下的模板
- 应用内部也可配置模板目录,配置方法如下:
- 应用下手动创建templates文件夹
- 在settings.py中开启应用模板功能
- INSTALLED_APPS配置项中的’APP_DIRS’值为True即可
- 注意:应用下templates和外层templates都存在时,django的查找模板规则:
- 优先查找外层templates目录下的模板
- 如外层templates目录下未找到匹配的模板,则按INSTALLED_APPS配置下的应用顺序从上往下逐层查找
- 为避免子应用中的模板文件重名 -> 解决方法:在子应用下的templates目录下创建一个与子应用同名的文件夹,将模板文件放至该同名文件夹下,并将视图函数中插入模板的路径更改为子应用名.模板文件名
例:from django.shortcuts import render# Create your views here. def index_view(request):return render(request,'news/index.html')
- 应用内部也可配置模板目录,配置方法如下:
五、ORM模型
5.1 数据库
- 模型层(Models) - 负责与数据库之间进行通信
- django配置MySQL
- 使用mysqlclient[版本mysqlclient 1.3.13以上]
- 安装前确认ubuntu是否已安装python3-dev 和 default-libmysqlclient-dev
- 使用命令检查是否安装sudo apt list --installed|grep -E 'libmysqlclient-dev|python3-dev’若无命令输出则需要安装
- 安装命令:sudo apt-get install python3-dev default-libmysqlclient-dev
- 安装mysqlclient: sudo pip install mysqlclient
- 如出现如下报错:unable to execute ‘x86_64-linux-gnu-gcc’: No such file or directory 则需在linux终端执行
sudo apt-get install gcc
- 使用pymysql
- 使用pip install pymysql 安装pymysql
- 对比:mysqlclient是C扩展类,安装麻烦,运行速度快;pymysql是纯python实现,安装简单,运行速度不及mysqlclient
- 创建项目对应的数据库
- 进入mysql数据库执行:
create database 数据库名 default charset utf8
#创建数据库设置默认编码格式为utf8- 通常数据库名保持跟项目名一致
- 在settings.py文件中进行数据库的配置
- 修改DATABASES 配置项的内容,将sqlite3改为mysql
DATABASES = {'default': {'ENGINE': 'django.db.backends.mysql', #指定数据库的存储引擎'NAME': 'mysql_demo', #指定要连接的数据库的名称'USER': 'root', #指定登陆到数据库的用户名'PASSWORD': 'mysql', #数据库登录密码'HOST': '127.0.0.1', #连接数据库的IP'PORT': '3306', #连接数据库的端口} }
ENGINE:常见的引擎还有:
‘django.db.backends.mysql’
‘django.db.backends.sqllite3’
‘django.db.backends.oracle’
‘django.db.backends.postgresql’
- 修改DATABASES 配置项的内容,将sqlite3改为mysql
- 进入mysql数据库执行:
5.2 模型类
- 模型
- 定义:是一个python类,它是由
django.db.models.Model
派生出的子类(即它必须继承至django.db.models.Model
) - 一个模型类代表着数据库的一张数据表
- 模型类中的每一个类属性都代表着数据库中的一个字段
- 模型是数据库交互的接口,是表示和操作数据库的方法和方式
- 定义:是一个python类,它是由
5.3 ORM框架
- 定义:ORM(object relational mapping) 对象关系映射,它是一种程序技术,允许使用类和对象对数据库进行操作,避免了通过sql语句操作数据库
- 作用:
- 建立模型类和表之间的对应关系,允许我们通过面向对象的方式来操作数据库
- 根据设计的模型类来生成数据库中的表格
- 通过简单的配置就可进行数据库的切换(切换settings.py文件中配置的数据库名即可)
- 优点:
- 只需要面向对象编程,不需要面向数据库编写代码
- 对数据库的操作都转换成对类属性和方法的操作
- 不用编写各种数据库的sql语句
- 实现了数据模型与数据库的解耦,屏蔽了不同数据库操作上的差异
- 不在关注用的数据库是mysql、oracle…等数据库的内部细节
- 通过简单的配置就可以轻松更换数据库,而不需要修改代码
- 只需要面向对象编程,不需要面向数据库编写代码
- 缺点:
- 对于复杂业务,使用成本较高(对于查询很复杂的业务,需要学习如何使用ORM来实现)
- 根据对象的操作转换成sql语句,根据查询的结果转化成对象,在映射的过程中会有性能损失
- ORM 映射图
- 模型示例:
- 在子应用中的模型类中添加模型类及字段
from django.db import models # Create your models here. class Book(models.Model):book_name = models.CharField(verbose_name='书名',max_length=50,default='')price = models.DecimalField(verbose_name='价格',max_digits=7,decimal_places=2)
- 数据库迁移
- 迁移是django用来同步对模型所做更改(添加字段,删除模型等)到同步到数据库的方式
- 生成迁移文件 - 执行
python manage.py makemigrations
将应用下的models.py文件生成一个中间文件,并保存在migrations文件夹中 - 执行迁移脚本程序 -
python manage.py migrate
执行迁移程序实现迁移,将每个应用下的migrations目录中的中间文件同步到数据库
- 生成迁移文件 - 执行
- 迁移生成的数据库表名为:
应用名_模型类类名
- 迁移是django用来同步对模型所做更改(添加字段,删除模型等)到同步到数据库的方式
- 创建模型类流程总结
- 创建应用
- 在应用下的models.py文件中编写模型类
from django.db import models class ModelName(models.Model):FieldName = models.FieldType(field-options) """FieldName:字段名,迁移后对应数据表中字段名FieldType:字段类型field-options:字段选项"""
- 迁移同步 makemigrations&migrate
注意: 任何关于表结构的修改,务必在对应模型类上进行修改; 向模型类中添加字段时需要设置默认值;
- 字段类型
字段类型 | 数据库类型 | 作用 | 参数 |
---|---|---|---|
BooleanField() | tinyint(1) | 使用True或False来表示值 | - |
CharField() | varchar | - | max_length:字符最大长度(必填参数) |
DateField() | date | 表示日期 |
auto_now:每次保存对象时, 自动设置该字段为当前时间(True/False); auto_now_add:当对象第一次被创建时自动设置当前时间(True/False); default:设置当前默认时间(取值:字符串格式时间,如’2021-7-1’) * 以上三个参数只能多选一 |
DateTimeField() | datetime(6) | 表示日期和时间 | 参数通DateField |
FloatField() | double | 使用小数表示值 | - |
DecimalField() | decimal(x,y) | 使用小数表示值 |
max_digits:位数总长度, 包括小数点后的位数, 该值必须大于等于decimal_places; decimal_places:小数点后的数字长度(即小数位数) *以上两个参数必选 |
EmailField() | varchar | 表示邮箱 | - |
IntegerField() | int | 表示整数 | - |
ImageField() | varchar(100) | 在数据库中为了保存图片的路径 | - |
TextField() | longtext | 表示不定长的字符数据 | - |
注:更多字段类型说明参考官方文档
- 字段选项
- 字段选项用于指定创建列的额外的信息;允许出现多个字段选项,多个选项之间用逗号隔开
字段选项 | 说明 |
---|---|
primary_key | 如果设置为True,表示该列为主键,如果指定一个字段为主键,则此数据库不会创建id字段 |
blank | 设置为True时,该字段可以为空(在管理后台中编辑数据的时候可以为空),设置为False时,该字段时必须填写的 |
null | 设置为True时,表示该列值允许为空,默认为False,如果此项为False时建议加入default选项来设置默认值 |
default | 设置所在列的默认值,如果字段null=false建议添加此项 |
db_index | 设置为True时,表示为该列增加索引 |
unique | 设置为True时,表示该字段在数据库中的值必须是唯一不能重复出现的 |
db_column | 指定列的名称,如果不指定的话则采用类属性名作为列名 |
verbose_name | 设置此字段在admin界面显示的名称,不设置则默认以类属性名显示 |
注:更多字段选项说明参考官方文档
模型类 - Meta类(控制表相关属性)
- 定义:使用内部Meta类来给模型赋予属性,Meta类下有很多内建的类属性,可对模型类做一些控制
- 常见Meta类的内建属性:
属性 作用 db_table=‘数据表名’ 修改该模型所用的数据表的名称,设置完成后需要立马更新同步数据库 verbose_name=‘单数名’ 给模型一个易于理解的名称(单数),用于显示在/admin管理界面中 verbose_name_plural=‘复数名’ 该对象复数形式的名称,用于显示在/admin管理界面中 #file:models.py from django.db import models class Books(models.Model):class Meta:db_table = 'book' #改变当前模型类对应的表名为book
- ORM 基本操作 包括数据库的增删改查(增加:Create、读取:Read、更新:Update、删除:Delete)
- ORM CRUD核心 -> 模型类.管理器对象
- 管理器对象: 每个继承自models.Model的模型类,都会有一个objects对象被同样继承下来,这个对象就叫管理器对象
class MyModel(models.Model):··· MyModel.objects.create(···) #objects就是管理器对象
创建数据 -> 创建数据中每一条记录就是创建一个数据对象
- 方案一:MyModel.objects.create(属性1=值1,属性2=值2)
- 成功:返回创建好的数据
- 失败:抛出异常
- 方案二:创建MyModel实例对象,并调用save()进行保存
obj = MyModel() obj.属性1 = 值1 obj.属性2 = 值2 obj.save() #执行save()后数据才会提交到数据库中
- django shell :在django中提供了一个交互式的操作项目交django shell,它能够在交互模式用项目工程的代码执行相应的操作;利用django shell 可以代替编写view的代码来进行直接操作 注意:项目代码发生变化时,需要重新进入django shell; 进入方式: python manage.py shell
- 方案一:MyModel.objects.create(属性1=值1,属性2=值2)
查询数据 -> 数据库的查询也需要使用管理器对象进行(可用QuerySet对象.query显示ORM转换为SQL语句的查询方法)
通过MyModel.objects管理器方法调用查询方法
方法 | 用法 | 作用 | 返回值 |
---|---|---|---|
all() | MyModel.objects.all() | 查询MyModel表中的所有数据,等同于select * from table |
QuerySet容器对象,内部存放MyModel实例 <QuerySet [<Book: Book object (1)>, <Book: Book object (3)>]> |
values (‘列1’, ’列2‘) |
MyModel.objects.values() | 查询部分列的数据并返回,等同于select 列1,列2 from table | QuerySet容器对象,内部存放字典,每个字典代表一条数据;格式为{‘列1’:值1,‘列2’:值2} |
values_list (‘列1’, ’列2‘) |
MyModel.objects.values_list() | 返回元组形式的查询结果,等同于select 列1,列2 from xxx | QuerySet容器对象,内部存放元组,会将查询出来的数据封装到元组中,再封装到查询集合QuerySet中,如果需要将查询结果取出,需要使用索引的方式取值 |
order_by (‘列1’, ’列2‘) |
MyModel.objects.order_by() | 与all()方法不同,它会用SQL语句的ORDER BY子句对查询结果进行根据某个字段选择性的进行排序,默认是按照升序排序,降序排序需要在列前面增加’-‘表示 | QuerySet容器对象,内部存放MyModel实例,将实例按指定字段进行排序(等同于MyModel.objects.all().order_by(‘列1’)) |
filter(条件) | MyModel.objects.filter(属性1=值1,属性2=值2)多个属性在一起时为”与“关系 | 返回包含此条件的全部的数据集 | QuerySet容器对象,内部存放MyModel实例 |
exclude(条件) | MyModel.objects.exclude(条件)多个属性在一起时为”与“关系 | 返回不包含此条件的全部的数据集 | QuerySet容器对象,内部存放MyModel实例 |
get(条件) | MyModel.objects.get(条件) | 返回满足条件的唯一一条数据,查询结果多余一条数据则抛出Model.MultipleObjectsReturned异常,查询结果如果没有数据则抛出Model.DoesNotExist异常 | QuerySet容器对象,内部存放MyModel实例 |
- 查询谓词
- 定义:做更灵活的条件查询时需要使用查询谓词
- 说明:每一个查询谓词是一个独立的查询功能
- 语法:类属性__查询谓词
查询谓词 | 说明 | 示例 |
---|---|---|
__exact | 等值匹配 |
Author.objects.filter(id__exact==1) 等同于 select * from author where id = 1
|
__contains | 包含指定值 |
Author.objects.filter(name__contains='w') 等同于 select * from author where name like '%w%'
|
__startwith | 以xxx开始 |
Author.objects.filter(name__startwith='w') 等同于 select * from author where name like 'w%'
|
__endwith | 以xxx结束 |
Author.objects.filter(name__endwith='w') 等同于 select * from author where name like '%w'
|
__gt | 大于指定值 |
Author.objects.filter(age__gt=50) 等同于 select * from author where age > 50
|
__gte | 大于等于指定值 |
Author.objects.filter(age__gte=50) 等同于 select * from author where age >= 50
|
__lt | 小于指定值 |
Author.objects.filter(age__lt=50) 等同于 select * from author where age < 50
|
__lte | 小于等于指定值 |
Author.objects.filter(age__lte=50) 等同于 select * from author where age <= 50
|
__in | 查找数据是否在指定范围内 |
Author.objects.filter(country__in=['中国','日本','韩国']) 等同于 select * from author where country in ['中国','日本','韩国']
|
__range | 查找数据是否在指定的区间范围内 |
Author.objects.filter(age__range=(30,50)) 等同于 select * from author where author between 35 and 50
|
更多查询谓词参考官方文档
ORM 更新操作
- 更新单个数据 - > 修改单个实体的某些字段值的步骤
- 查 -> 通过get()得到要修改的实体对象
- 改 -> 通过对象.属性 的方式修改数据
- 保存 -> 通过对象.save()保存数据
- 批量更新数据 -> 直接调用QuerySet的update(属性=值)实现批量修改
#示例 #将id大于3的所有图书价格定位0元 books = Book.objects.filter(id__gt==3) books.update(price=0)
ORM 删除操作
- 单个数据删除 :
- 查 -> 查找查询结果对应的一个数据对象
- 删 -> 调用这个数据对象的delete()方法实现删除
try:auth = Author.objects.get(id=1)auth.delete() except:print("删除失败")
- 批量数据删除
- 查 -> 查找查询结果中满足条件的全部QuerySet查询集合对象
- 删 -> 调用查询集合对象的delete()方法实现删除
#删除年龄≥65的全部信息 auths = Author.objects.filter(age__gt=65) auths.delete()
- 伪删除
- 通常不会轻易在业务里把数据真正删掉,取而代之的是做伪删除,即在表中添加一个布尔型字段(is_active、is_delete),默认是True;执行删除时,将要删除数据的(is_active、is_delete)字段值设为False
- 注意:用伪删除时,确保显示数据的地方,均加了is_active/is_delete=True的过滤查询
F对象
- 定义:一个F对象代表数据库中某条记录的字段的信息
- 作用:
- 通常是对数据库中的字段值在不获取的情况下进行操作
- 用于类属性(字段)之间的比较
- 语法:
from django.db.models import F f('列名')
Q对象
- 作用:在条件中用来实现逻辑或 | 、逻辑非~、逻辑与&等操作
- 语法:
from django.db.models import Q Q(条件1) | Q(条件2) #条件1或者条件2成立 Q(条件1) & Q(条件2) #条件1和条件2同时成立 Q(条件1) &~ Q(条件2) #条件1成立且条件2不成立
F对象与Q对象举例见链接
聚合查询和原生数据库操作
- 聚合查询
- 定义:聚合查询是指针对一个数据表中的一个字段的数据进行部分或全部进行统计查询,查询数据表中某一列的平均值、查询某个值出现的次数等都叫聚合查询
- 分类:聚合查询又分为整表聚合和分组聚合
- 整表聚合:指将全部数据进行集中统计查询
- 常见的聚合函数有:Sum,Avg,Count,Max,Min
- 导入方法:
from django.db.models import *
- 语法:
Mymodel.objects.aggregate(结果变量名=聚合函数('列'))
- 返回结果:结果变量名和值组成的字典,格式为:
{"结果变量名":“值”}
- 分组聚合:指通过计算查询结果中每一个对象所关联的对象集合(先按字段进行分组),从而得出总计值(也可以是平均值或总和),即为查询集的每一项生成聚合
- 语法:QuerySet.annotate(结果变量名=聚合函数(‘列’))
- 返回结果: QuerySet
- 例:
- 通过先用查询结果MyModel.objects.values 查找查询要分组聚合的列
MyModel.objects.values('列1','列2')
- 通过返回结果的QuerySet.annotate方法分组聚合得到分组结果
QuerySet.annotate(结果变量名=聚合函数('列'))
from books.models import Book from django.db.models import Count pub_set = Book.objects.values('pub') #pub_set = <QuerySet [{'pub': '清华大学出版社'}, {'pub': '清华大学出版社'}, {'pub': '机械工业出版社'}, {'pub': '机械工业出版社'}, {'pub': '清华大学出版社'}]> pub_count_set = pub_set.annotate(mycount=Count('pub')) #pub_count_set =<QuerySet [{'pub': '清华大学出版社', 'mycount': 3}, {'pub': '机械工业出版社', 'mycount': 2}]>
- 整表聚合:指将全部数据进行集中统计查询
- 聚合查询
原生数据库操作 - raw():django也支持直接用sql语句的方式通信数据库 不建议使用
- 查询::使用MyModel.objects.raw()进行数据库查询操作
- 语法:MyModel.objects.raw(sql语句,拼接参数)
- 返回值:RawQuerySet集合对象【只支持基础操作,比如循环操作】
books = models.Book.objects.raw('select * from bookstore_book') for book in books:print(book)
- SQL 注入:使用原生语句时要小心SQL注入
- 定义:用户通过数据上传,将恶意的sql语句提交给服务器,从而达到攻击效果
- 案例1:用户在搜索好友的表单框里输入
‘1 or 1 = 1’
s1 = Book.objects.raw('select * from bookstore_book where id = s%'%('1 or 1 = 1'))
- 攻击结果:可查询出所有用户数据
- SQL 注入防范:使用
MyModel.objects.raw(sql语句,拼接参数)
中的拼接参数,拼接参数是一个列表,将所有需要拼接的值写入列表中 - 例:
s1= Book.objects.raw('select * from bookstore_book where id = %s',['1 or 1 = 1'])
原生数据库操作 - cursor():django也支持直接用sql语句的方式通信数据库
- 完全跨过模型类操作数据库 - 查询/更新/删除
- 使用方法:
- 导入cursor所在的包
from django.db import connection
- 用创建cursor类的构造函数创建cursor对象,再使用cursor对象,为保证在出现异常时能释放cursor资源,通常使用with语句进行创建操作
from django.db import connection with connection.cursor() as cur:cur.execute('SQL语句','拼接参数')
- 示例:
#用SQL语句将id为10的书的出版社改为“xxx出版社” from django.db import connection with connection.cursor() as cur:cur.execute('update bookstore_book set pub = "xxx出版社" where id = 10;')
拓展知识
- 自定义QuerySet中的输出格式:
#在Book模型类中定义: def __str__(self):return '%s_%s_%s_%s'%(self.title,self.price,self.pub,self.market_price) #自定义输出格式 #在shell模式下得到如下显示 >>> from books.models import Book >>> a1 = Book.objects.all() >>> a1 <QuerySet [<Book: python_20.00_清华大学出版社_25.00>, <Book: Django_70.00_清华大学出版社_75.00>, <Book: JQuery_90.00_机械工业出版社_85.00>, <B出版社_65.00>, <Book: HTML5_90.00_清华大学出版社_105.00>]>
数据库操作常见问题汇总
- 问题1:当执行python manage.py makemigrations 出现如下迁移错误
You are trying to add a non-nullable field 'pub' to book without a default; we can't do that (the database needs something to populate existing rows). Please select a fix:1) Provide a one-off default now (will be set on all existing rows with a null value for this column)2) Quit, and let me add a default in models.py Select an option:
- 问题分析:
- 当对模型类新添加一个字段时可出现该错误
- 原理是添加新字段后,数据库不知道原来已有数据对于新建字段该如何赋值,所以新增字段时,必须要添加default默认值
- 问题改善:
- 如果选择选项1)提供一个临时默认值,这个默认值会初始化到迁移文件中,不会体现在模型类中,别人在代码维护的时候出现黑匣子,导致有些现象与代码不符
- 选择选项2)退出生成迁移文件过程,重新修改models.py,新增加一个default=xxx的默认值(推荐使用第二种方法)
- 问题2:数据库迁移文件混乱,数据库中的django_migrations表记录了migrate的所有记录,当出现多人协作时,每个人的迁移文件版本不同,在提交时就会出现迁移文件混乱
- 问题分析:数据库中的django_migrations表记录了migrate的所有记录,项目各应用中的migrate文件应与之相对应
- 问题改善( 只针对本地测试时):
- 删除所有migrations里所有的000?_xxx.py(init.py文件除外)
- 删除数据库
sql>drop database mywebdb;
- 重新创建数据库
sql>create datebase mywebdb default charset=utf8;
- 重新生成migrations里所有的000?_xxx.py
python manage.py makemigrations
- 重新更新数据库
python manage.py migrate
六、admin管理后台
6.1 admin配置步骤
- 创建后台管理账号 - 该账号为管理后台最高权限账号
- termin切换到项目目录下执行 ->
python manage.py createsuperuser
#命令行执行 >>>python manage.py createsuperuser 用户名 (leave blank to use 'sixstar'): topgh #此处输入用户名 电子邮件地址: 634890284@qq.com #此处输入邮箱 Password: #此处输入密码 (密码过于简单则会提示密码太简单) Password (again): #此处再次输入密码 密码长度太短。密码必须包含至少 8 个字符。 这个密码太常见了。 密码只包含数字。 Bypass password validation and create user anyway? [y/N]: y #绕过密码验证并创建用户 Superuser created successfully.
- termin切换到项目目录下执行 ->
6.2 注册自定义模型类
- 注册步骤:
- 在子应用app文件夹中的admin.py文件中导入注册要管理的模型类,如:
from .models import Book
- 调用 admin.site.register 方法进行注册 如:
admin.site.register(自定义模型类)
- 注册后会在admin首页显示注册的自定义模型类
- 点击第二个Books 进入book管理页面,管理页面中显示的Book信息与在定义Book模型类时,与定义的def__str__(self)方法中的显示样式一致
- 在子应用app文件夹中的admin.py文件中导入注册要管理的模型类,如:
- 模型管理器类
作用:为后台管理界面添加便于操作的新功能
说明:后台管理器继承自django.contrib.admin 里的ModelAdmin类
使用方法:1.在子应用app下的admin.py文件中定义模型管理器类,模型管理器类的名字可自定义,一般按照一个模型对应一个模型管理器类
class XXXXManager(admin.ModelAdmin): "自定义模型类属性"
绑定注册模型管理器和模型类:
from django.contrib import admin from .models import * admin.site.register(YYYY,XXXXManager) #绑定YYYY模型类与管理器类XXXManger
常用模型管理器类的类属性
类属性 说明 示例 list_display=[field1,field2…] 用于控制哪些字段会显示在Admin的修改列表页面中 list_display = ['id','title','pub','price']
list_display_links=[field1] 控制list_display中的字段,哪些可以链接到修改页,默认情况下link加在id上 list_display_links = ['title']
list_filter=[field1,field2…] 添加过滤器(指定的字段作为过滤条件),添加后会在页面的右侧显示过滤器 list_filter = ['pub']
search_fields=[field1,field2…] 添加搜索框(指定的字段可进行模糊查询) search_fields = ['title']
list_editable=[field1,field2…] 用于添加可在列表页编辑的字段,添加的字段必须在list_display中,并且该字段不能在list_display_links中 list_display = ['id','title','pub','price']
例
#file:books/admin.py from django.contrib import admin from django.contrib import admin from .models import Book, Authoradmin.site.register(Book) #注册Book模型类class AuthorManager(admin.ModelAdmin): #自定义author模型管理器类list_display = ['id','name','age'] #admin后台中修改列表页面显示字段list_editable = ['age'] #age字段可在列表页编辑class Meta:verbose_name = '作者' #admin中显示的模型类名字verbose_name_plural = verbose_name #单复数同名 admin.site.register(Author,AuthorManager) #绑定模型类与模型管理器类,并注册
更多关于模型管理器类的类属性参考官方文档
6.3 关系映射
- 定义:在关系型数据库中,通常不会把所有数据都放在同一张表中,不易于扩展
- 常见的关系映射有:
- 一对一映射:一个身份证对应一个人
- 一对多映射:一个班级可以有多个学生
- 多对多映射:一个学生可以报多个课程,一个课程可以有多个学生学习
一对一【创建表】:
- 定义:一对一是表示现实事物间存在的一对一的对应关系
- 创建语法:OneaToOneField(类名,on_delete=xxx)
class A(model.Model):…… class B(model.Model):属性 = models.OneToOneField(A, ondelete=xxx)
- on_delete - 外键删除方式
- models.CASCADE: 级联删除;Django 模拟了 SQL 约束 ON DELETE CASCADE 的行为,并删除包含 ForeignKey 的对象,。
- models.PROTECT: 保护模式;抛出ProtectedError,以阻止被引用对象的删除;【等同于mysql默认的RESTRICT】
- SET_NULL: 置空模式,删除的时候,外键字段被设置为空,前提就是blank=True, null=True,定义该字段的时候,允许为空
- SET_DEFAULT: 设置默认值,删除的时候,外键字段设置为默认值,所以定义外键的时候必须加上一个默认值。
更多外键删除方式参考官方文档
- 示例:
from django.db import models class Author(models.Model):name = models.CharField(verbose_name='作者',max_length=11) class AuthorWife(models.Model):name = models.CharField(verbose_name='妻子',max_length=11)author = models.OneToOneField(Author,verbose_name='妻子的丈夫',on_delete=models.CASCADE) #通常外键属性就叫关联的模型类类名小写,在数据库中外键字段名跟类属性名有区别,默认是以类属性名 + '_' +id作为字段名
- 通常把外键类属性名就叫关联的模型类类名小写;在数据库中外键字段名跟类属性名也有区别,默认是以 类属性名 + ‘_’ +id
一对一【创建数据】
- 无外键的模型类创建数据
author1 = Author.objects.create(name = '王老师')
- 有外键的模型类创建数据
- wife1 = AuthorWife.objects.create(name=‘王夫人’,author=author1) #以类属性名创建数据,必须传入关联外键的实例对象
- wife1 = AuthorWife.objects.create(name=‘王夫人’,author_id=1) #直接用外键字段名,必须传入关联对象对应的主键值
- 无外键的模型类创建数据
一对一【查询数据】
- 正向查询:直接通过外键属性查询 [通过有外键的表查询没有外键的表]
#通过wife查找author from .models import AuthorWife wife = Wife.objects.get(name='王夫人') print(wife.name,'的老公是',wife.author.name) #wife.author返回的是wife所关联的author对象,调用author对象的name属性获得对应的值
- 反向查询:没有外键属性的一方,可以调用反向属性(有外键的模型类类名)查询到关联的另一方,反向关联属性为
实例对象.引用类名(小写)
,如:作家的反向引用为’作家对象.wife’;当反向引用不存在时,则会触发异常author1 = Author.objects.get(name='王老师') author1.wife.name
- 正向查询:直接通过外键属性查询 [通过有外键的表查询没有外键的表]
一对多【创建表】
- 定义:表示事物间存在一对多的对应关系(如:一个学校对应多个班级,一个班级对应多个学生);一对多需要明确出具体角色,在多表上设置外键
- 语法:
- 当一个A类对象关联多个B类对象时;Foreignkey必须指定on_delete模式
class A(models.Model): #一··· class B(models.Model): #多属性 = models.Foreignkey("一"的模型类,on_delete=xxx) #on_delete指明在删除A时,B表对应字段如何处理
- 示例
#file:otm/models.py from django.db import models class Publisher(models.Model):"""出版社【一】"""name = models.CharField('名称',max_length=50,unique=True) class Book(models.Model):"""书【多】"""title = models.CharField('书名',max_length=50)publisher = models.ForeignKey(Publisher,on_delete=models.CASCADE)
一对多【创建数据】
- 先创建’一’,再创建’多’
from .models import * pub1 = Publisher.objects.create(name="清华大学出版社") Book.objects.create(title="C++",publisher=pub1) #以类属性名创建数据,需要传入关联外键的对象 Book.objects.create(titel="python",publisher_id=1) #以字段名创键数据,需要传入关联对象的主键值
- 先创建’一’,再创建’多’
一对多【查询数据】
正向查询:直接通过外键属性查询 [通过有外键的表查询没有外键的表]
#通过Book查询publisher abook = Book.objects.get(id=1) print(abook.title,'的出版社是',abook.publisher.name) #abook.publisher返回的是abook所关联的publisher对象,直接调用publisher对象的name属性
反向查询:没有外键属性的一方,可以调用反向属性(
有外键的模型类类名(小写)_set
)查询到关联的另一方pub1 = Publisher.objects.get(name='清华大学出版社') books = pub1.book_set.all() #通过book_set获取pub1对应的多个Book数据对象,pub.book_set得到的是RelatedManager关联管理器对象 #books = Book.objects.filter(Publisher=pub1) #也可用此方式获取 print("清华大学出版社出版的书有:") for book in books:print(book.title)
多对多【创建表】
- 定义:多对多表达对象之间多对多复杂关系,如:一个学生可以报名多门课程,一门课程可以对应多名学生
- 实现方式:
- MySQL中创建多对多需要依赖第三张表来实现
- django中无需手动创建第三张表,django自动完成
- 语法:在关联的两个类中的任意一个类增加一个多对多属性(为便于记忆,属性名可用所关联的模型类小写的复数形式)
属性名 = models.ManyToManyField(MyModel)
#MyModel:多对多关联的模型类- 示例
#创建一本书对应多个作家,一个作家对应多本书 class Author(models.Model):"""作家模型类"""name = models.CharField('作家',max_length=20)def __str__(self):return self.name class Book(models.Model):"""书模型类"""title = models.CharField('书名',max_length=20)authors = models.ManyToManyField(Author) #设置多对多属性,设置在任意模型类中均可def __str__(self):return self.title
- 示例
多对多【创建数据】
- 方案一:先创建无多对多属性的数据表在关联有多对多属性的数据表,利用反向属性创建
author1 = Author.objects.create(name='王老师') author2 = Author.objects.create(name='李老师') #王老师和李老师共同写了一本书python book1 = author1.book_set.create(title='python') author2.book_set.add(book1) #add一个obj
- 方案二:先创建有多对多属性的表再关联无多对多属性的数据表
book = Book.objects.create(title='python1') #郭老师和李老师都参与了python1 的创作 author3 = book.authors.create(name='郭老师') book.authors.add(author1)
- 方案一:先创建无多对多属性的数据表在关联有多对多属性的数据表,利用反向属性创建
多对多【查询数据】
- 正向查询:有多对多属性的对象查询另一方,此时的多对多属性等价于objects
book.authors.all() -> 获取 book 对应的所有的author的信息 book.authors.filter(age__gt=80) -> 获取book对应的作者中年龄大于80岁的作者的信息
- 反向查询:无多对多属性的对象查询有多对多属性的一方,利用反向属性有多对多属性的类名(小写)_set
author.book_set.all() author.book_set.filter()
- 正向查询:有多对多属性的对象查询另一方,此时的多对多属性等价于objects
七、会话保持
- 会话
- 定义:从打开浏览器访问一个网站,到关闭浏览器结束此次访问,称之为一次会话
- HTTP协议是无状态的,导致会话状态难以保持
- Cookies和Session就是为了保持会话状态而诞生的两个存储技术
7.1 Cookies
- cookies定义:是保存客户端浏览器上的存储空间
- Cookies特点
- cookies在浏览器上是以键值对的形式进行存储的,键和值都是以ASCII字符串的形式存储(不能是中文字符串)
- 存储的数据带有生命周期
- cookies中的数据是按域存储隔离的,不同的域之间无法访问
- cookies的内部的数据会在每次访问此网址时都会携带到服务器端,如果cookies过大会降低响应速度
- Cookies的使用 - 存储
- 语法:
HttpResponse.set_cookie(key,value='',max_age=None,expires=None)
- key:cookie 的名字
- value:cookie的值
- max_age:cookie存活时间,以秒为单位
- expires:具体的过期时间
- 当不指定max_age和expires时,关闭浏览器时此数据失效
- 创建cookie示例:
#为浏览器添加键为my_var1,值为123,过期时间为1小时的cookie response = HttpReponse('已添加名为my_var1的cookie') response.set_cookie('may_var1','123',3600) return response
- 修改cookie示例:
#为浏览器添加键为my_var1,修改值为456,过期时间为2小时的cookie response = HttpResponse('已修改名为my_var1的cookie') response.set_cookie('may_var1','456',3600*2) return response
- 语法:
- cookies 使用 - 删除&获取
- 删除cookies:通过
HttpResponse.delete_cookie(key)
- 删除指定的key的cookie,如果key不存在则什么也不发生
- 获取cookies:通过request.COOKIES绑定的字典(dict)来获取客户端的COOKIES数据;
value = request.COOKIES.get('cookies名','默认值')
#可用get()方法获取也可用索引方式获取
- 删除cookies:通过
7.2 session
定义:session是在服务器上开辟一段空间用于保留浏览器和服务器交互的重要数据
实现方式:
- 使用session需要在浏览器客户端启动cookie,且在cookie中存储sessionid
- 每个客户端都可以在服务器端有一个独立的session
- 注意:不同的请求者之间不会共享这个数据,与请求者一一对应
settings.py 中配置session(默认是自动配置的)
- 向INSTALLED_APPS列表中添加
'django.contrib.sessions'
- 向MIDDLEWARE列表中添加``
- SESSION_COOKIE_AGE:指定sessionid在cookies中的保存时长(默认是2周)
- SESSION_EXPIRE_AT_BROWSER_CLOSE =True:设置浏览器关闭时,session就失效,默认是False
- 注意:django中的session数据是存储于数据库中的,所以使用session前需要确保已经执行过migrate;django会将session存储于数据库中的django_session表中
- django中session的问题:
- django_session表是单表设计,且该数据量是持续增长【浏览器故意删除sessionid,但是数据库中的数据并未删除】
- 可以通过执行
python manage.py clearsessions
来删除过期的session数据
- 向INSTALLED_APPS列表中添加
session的使用
- session对象是一个类似于字典的SessionStore类型的对象,可以用类似于字典的方式进行操作
- session可以存储如:字符串、整型、字典、列表等
- 添加session到服务器
request.session['KEY']:VALUE
- 获取session的值:
value = request.session['KEY']
;value = request.session.get('KEY',默认值)
- 删除session:
del request.session['KEY']
Cookies与Session对比
种类 存储位置 安全性 用途 Cookies 浏览器 相对不安全 一般用于存储长期数据 session 服务器 相对安全 一般用于存储短期数据
7.3 缓存
缓存
- 定义:缓存是一类可以更快的读取数据的介质的统称,也指其他可以加快数据读取的存储方式,一般用来存储临时数据,常用介质是读取速度很快的内存
- 意义:视图渲染有一定的成本,数据库的频繁查询过高;所以对于低频变动的页面可以考虑使用缓存技术,减少实际渲染次数,用户拿到响应的时间成本会更低
- 案例分析:
from django.shortcuts import render def index(request):#时间复杂度极高的渲染book_list = Book.objects.all() #-> 此处假设用时2sreturn render(request,'index.html',local()) #此处的页面渲染会在book_list查询到结果后才执行
- 优化思想:(转至官网)
given a URL, try finding that page in the cache if the page is in the cache:return the cached page else:generate the pagesave the generated page in the cache (for next time)return the generated page
- 缓存应用场景:
- 博客列表页
- 电商商品详情页
- 场景特点:缓存的地方,数据变动频率小
django中设置缓存 - 数据库缓存
- 将缓存的数据存储在数据库中
- 说明:尽管存储的介质没有更换,但是当把一次负责查询的结果直接存储到另一张表里,比如多个条件的过滤查询结果,可避免重复进行复杂查询,提升效率。
- 设置settings.py,添加CACHES配置
CACHES = {'default':{'BACKEND':'django.core.cache.backends.db.DatabaseCache', #引擎'LOCATION':'my_cache_table',#设置缓存使用的数据表,设置表名为:my_cache_table'TIMEOUT':300 #设置缓存保存时间 单位秒,默认值为300'OPTIONS':{'MAX_ENTRIES':300, #缓存最大数据条数'CULL_FREQUENCY':2 #缓存达到最大条数时,删除1/X的缓存条数}} }
- 注:数据库缓存所使用的数据表需要手动创建,进入django shell执行
python manage.py createcachetable
,表名为CACHES里面设置的表名 - 数据库迁移后会在数据库中自动生成my_cache_table数据表,数据表中字段:
- cache_key: 缓存的键
- value: 缓存的值
- expires: 缓存的过期时间
django中设置缓存 - 本地内存缓存
- 数据缓存到服务器内存中
- 配置样例:
CACHES = {'default': {'BACKEND': 'django.core.cache.backends.locmem.LocMemCache','LOCATION': 'unique-snowflake', #内存寻址 - 使用的是雪花算法} }
django中设置缓存 - 文件系统缓存
- 将缓存的数据存储到本地文件中
- 配置样例:
CACHES = {'default': {'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache','LOCATION': '/var/tmp/django_cache', #缓存文件夹的路径# 'LOCATION': 'c:\test\cache', #windows 下示例} }
django中使用缓存 - 整体缓存策略
- 在视图函数中:将整个视图函数全部缓存到数据库中,如需对某个视图进行增加缓存,直接在该视图上使用cache_page装饰器即可
from django.views.decorators.cache import cache_page @cache_page(30) -> 单位s,当前视图缓存有效期 def my_view(request):···
- 逻辑:第一次请求时将该视图的整个response存入到缓存中,下一次请求时就先检查缓存中是否有需要的response,如果有则不再进入视图函数中处理。
- 在路由中:在需要增加缓存的视图调用位置增加缓存
from django.views.decorators.cache import cache_page urlpatterns = [path('page/',cache_page(60)(my_view)),#逻辑与在试图函数中使用相同 ]
- 缺点:1. 当对整个视图函数进行缓存后,下一次请求时,缓存未过期,请求数据会直接走缓存,如果视图函数中有相关权限校验,则无法进行校验(如:博客网站,博主访问后会将私有博客、公有博客一并进入缓存,访客直接访问的话就不会走视图函数进行身份验证,也能访问到博客的私有博客);2. 删除缓存成本过高,几乎无法得知缓存的key,无法进行主动删除,容易导致出现新旧数据不一致的情况(编辑后的数据无法及时更新到缓存中去)
- 在视图函数中:将整个视图函数全部缓存到数据库中,如需对某个视图进行增加缓存,直接在该视图上使用cache_page装饰器即可
django中使用缓存 - 局部缓存
使用缓存API引入
- 方式一:使用caches[‘CACHE配置key’] 导入具体对象
from django.core.cache import caches cache1 = caches['default'] #default -> settings文件中配置的CACHES的键名 cache2 = caches['myalias']
- 方式二:直接导入CACHES配置项中的‘default’项:
from django.core.cache import cache
缓存API相关方法:
作用 参数 返回值 cache.set(key,value,timeout) 存储缓存 key : 缓存的key,字符串类型
value : python对象
timeout : 缓存存储时间(s),默认为CACHES中的TIMEOUT值cache.get(key) 获取缓存 key : 缓存的key,字符串类型 cache.add(key, value) 存储缓存,只在key不存在时生效 key : 缓存的key,字符串类型
value : python对象cache.get_or_set(key,value,timeout) 如果未获取到数据则执行set操作 key : 缓存的key,字符串类型
value : python对象
timeout : 缓存存储时间(s),默认为CACHES中的TIMEOUT值cache.set_many(dict,timeout) 批量存储缓存 dict : 缓存的key和value的字典
timeout : 缓存时间(s)cache.get_many(key_list) 批量获取缓存 key_list : 包含key的列表 cache.delete(key) 删除key的缓存数据 key : 缓存的key,字符串类型 cache.delete_many(key) 批量删除缓存 key_list : 包含key的列表
浏览器缓存策略
- 强缓存:不会向服务器发送请求,直接从缓存中读取资源
- 响应头 - Expires
- 定义:缓存过期时间,用来指定资源到期的时间,是服务器端的具体的时间点
- 样例:Expires : Thu,02 Apr 2030 05:14:08 GMT
- 响应头 - Cache-Control
- 作用:在HTTP/1.1 中,Cache-Control主要用于控制网页缓存,比如当
Cache-Control:max-age=120
代表请求创建时间后的120秒,缓存失效 - 说明:目前服务器都会带着这两个头同时响应给浏览器,浏览器优先使用Cache-Control
- 作用:在HTTP/1.1 中,Cache-Control主要用于控制网页缓存,比如当
- 响应头 - Expires
- 协商缓存 -> 在强缓存的基础上衍生出来的
- 强缓存中的数据一旦过期,还需要跟服务器进行通信,从而获取最新数据;当强缓存的数据是一些静态文件、大图片等这类比较费带宽且不易变化的数据,强缓存到期后,浏览器会去跟服务器协商,当前缓存是否可用,如果可用,服务器不必返回数据,浏览器继续使用原来缓存的数据,如果文件不可用,则返回最新数据
- 响应头:Last-Modified 和 If-Modifield-Since 请求头
- 说明:
- Last-Modified为文件的最近修改时间,浏览器第一次请求静态文件时,服务器如果返回Last-Modified响应头,则代表该资源为需协商的缓存
- 当缓存到期后,浏览器将获取到的Last-Modified值作为请求头If-Modifield-Since的值,与服务器发请求协商,服务端返回304响应码[响应体为空],代表缓存继续使用,200响应码代表缓存不可用[响应体为最新资源]
- 缺点:不够精确,Last-Modified是根据修改时间来进行判断缓存是否可继续使用,单位为秒;如果文件的修改时间极短,Last-Modified就发现不了文件已经更改
- 响应头 ETag响应头和If-None-Match 请求头
- 说明:
- ETag是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成),只要资源有变化,ETag就会重新生成(唯一标识为哈希值)
- 缓存到期后,浏览器将ETag响应头的值作为If-None-Match请求头的值,给服务器发请求协商;服务器接到请求头后,比对文件标识,不一致则认为资源不可用,返回200响应码代表缓存不可用[响应体为最新资源],可用则返回304响应码
- 缺点:请求时会对文件计算哈希值,会占用计算资源
- 强缓存:不会向服务器发送请求,直接从缓存中读取资源
八、中间件
8.1 中间件的定义
- 定义:中间件是django请求/响应处理的钩子框架,它是一个轻量级的、低级的插件系统,用于全局改变django的输入或输出
- 中间件以类的形式体现
- 每个中间件组件负责一些特定的功能,例如:django包含一个中间件组件AuthenticationMiddleware,她使用会话将用户与请求关联起来
8.2 中间件使用
编写中间件:
- 中间件类必须继承自
django.utils.deprecation.MiddlewareMixin
类 - 中间件类必须实现下列五个方法中的一个或多个
process_request(self,request)
:- 作用:执行主路由之前被掉用,在每个请求上调用,返回None(请求通过)或者HttpResponse对象(请求不通过)
- 用途:过滤请求
process_view(self,request,callback,callback_args,callback_kwargs)
:- 作用:callback:为视图函数;callback_args:视图函数的位置参数,callback_kwargs:视图函数的关键字参数;调用视图之前被调用,在每个请求上调用,返回None(请求通过)或者HttpResponse对象(请求不通过)
- 用途:用于代码层面的替换和过滤,这个方法可以拿到视图函数的参数
process_response(self,request,response)
:- 作用:response:即是视图函数的响应对象;在所有响应返回浏览器之前被调用,在每个请求上调用,返回HttpResponse对象
process_exception(self,request,exception)
:- 作用:处理过程中抛出异常时被调用,返回一个HttpResponse对象
- 用途:用于一般用于捕获发生的异常,并将其邮件发送给开发人员
process_template_response(self,request,response)
:- 作用:在视图函数执行完毕,且视图函数返回的对象中包含render方法时被调用;该方法需要返回实现了render方法的响应对象
- 注:中间件中的大多数方法在返回None时表示忽略当前操作进入下一项事件,当返回HttpResponse对象时,表示此请求结束,直接返回给客户端
- 中间件类必须继承自
注册中间件:
- 在settings.py文件中进行注册
#file:settings.py MIDDLEWARE= [···'MyMiddleWare', ]
- 注意:配置为列表形式,中间件被调用时,在请求视图被处理前,中间件由上至下依次执行;在请求视图被处理后,中间件由下至上依次执行
- 在settings.py文件中进行注册
示例:
- 添加过滤请求次数中间件
#file:mymiddleware.py from django.http import HttpResponse from django.utils.deprecation import MiddlewareMixin import re class MWare(MiddlewareMixin):count_dict = {} #创建用于统计次数的字典def process_request(self,request):request_ip = request.META['REMOTE_ADDR'] #获取请求IPrequest_url = request.path_info #获取请求URLif re.match(r'^/test',request_url): #匹配请求是否以/test开头times = self.count_dict.get(request_ip,0) #查询当前IP的请求次数,默认为0self.count_dict[request_ip]= times + 1 #请求次数 + 1if times < 5: #如果请求次数<5次,请求正常通过returnelse: #如果请求次数>5次,则返回HttpResponse,阻止请求return HttpResponse("访问次数超过5次,请求失败")else: #如果不是以/test开头则直接正常通过return
- 注册中间件
#file:settings.py 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','middleware.mymiddleware.MWare', ]
- 添加过滤请求次数中间件
附django请求流程图
8.3 CSRF - 跨站伪造请求攻击
- 定义:某些恶意网站上包含链接、表单按钮或者JavaScript,它们会利用登陆过的用户在浏览器中的认证信息试图在你的网站上完成某些操作,这就是跨站请求伪造(CSRF:Cross-Site-Request-Forgey)
- CSRF 防范:
- django采用比对 ‘暗号’ 机制防范攻击
- 原理:
- Cookies中存储暗号1,模板中表单里藏着暗号2,用户只有在本网站下提交数据,暗号2 才会随着表单提交给服务器,django对比两个暗号,对比成功,则认为时合法请求,否则是违法请求 - 403响应码
- 配置步骤:
- 在settings.py文件中确认MIDDLEWARE中django.middleware.csrf.CsrfViewMiddleware是否打开
- 在模板中的form标签下添加如下标签
{% csrf_token %}
- 示例:
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>CSRF-TEXT</title> </head> <body> <form action="/test_csrf" method="post">{% csrf_token %}<input type="text" name="test"><input type="submit" value="提交" name="key"> </form> </body> </html>
- 特殊说明(局部开放csrf):
- 如果某个视图不需要django进行csrf保护,可以用装饰器关闭对此视图的检查
- 样例:
from django.views.decorators.csrf import csrf_exempt @csrf_exempt #使用装饰器关闭csrf对此试图的检查 def my_view(request):return HttpResponse('hello world')
九、网页分页功能
- 分页定义
- 定义:分页是指在web页面有大量数据需要显示,为了阅读方便,在每页页中只显示部分数据
- 优点:方便阅读,减少数据提取量,减轻服务器压力
- django实现:
- django提供了Paginator类可以方便的实现分页功能
- Paginator类位于:
django.core.paginator
模块中
- Paginator对象
- 功能:负责分页数据整体管理
- 语法:
paginator = Paginator(object_list , per_page)
- 参数:
- object_list:需要分页数据的对象列表(也可以是QuerySet对象)
- per_page:每页数据条数
- 返回值:
- Paginator的对象
- 参数:
- Paginator对象的属性:
- count:需要分页数据的对象总数
- num_pages:分页后的页面总数
- page_range:从1开始的range对象,用于记录当前页码数
- per_page:每页显示数据的个数
- Paginator对象的方法
- paginator对象.page(number):
- 参数number为页码信息(从1开始)
- 返回当前number页对应的页信息
- 如果提供的页不存在,则抛出InvalidPage异常
- InvalidPage异常
- 总的异常基类,包含以下两个异常子类
- PageNotAnInteger:向page()中传入的number不是整数的值时抛出
- EmptyPage:当向page()中传入的number是一个有效值,但是那个页面上没有任何对象时抛出
- 总的异常基类,包含以下两个异常子类
- paginator对象.page(number):
- page对象
- 定义:负责具体某一页的数据的管理
- 语法:paginator对象的page()方法返回的就是page对象
- page = paginator.page(页码)
- 属性:
- object_list:当前页上所有数据对象的列表
- number:当前页的序号,从1开始
- paginator:当前page对象相关的paginator对象(查看是哪个paginator对象生成的page对象)
- has_next():如果有下一页,返回True
- has_previous():如果有上一页,返回True
- has_other_pages():如果有上一页或下一页返回True
- next_page_number():返回下一页的页码,如果下一页不存在,抛出InvalidPage异常
- previous_page_number():返回上一页的页码,如果上一页不存在,抛出InvalidPage异常
- 示例
#file:urls.py from django.urls import path from . import views urlpatterns = [path('test_page', views.test_page), #创建路由 ]
#file:views.py def test_page(request):#请求页数从URL中获取p = request.GET.get('page',1)data_list = [str(i) for i in range(20)] #生成列表数据#创建Paginator对象paginator = Paginator(data_list,per_page=2)#创建page对象page = paginator.page(int(p))return render(request,'test_page.html',locals())
<!--file:text_page.html--> <!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>分页显示</title> </head> <body> {% for p in page.object_list %} <!--从page对象中获取当前页的数据-->{{ p }} {% endfor %} {% if page.has_previous %}<a href="/test_page?page={{ page.previous_page_number }}"> 上一页</a> {% else %}<p>上一页</p> {% endif %} {% for pg in paginator.page_range %}{% if pg == page.number %}{{ pg }}{% else %}<a href="test_page?page={{ pg }}">{{ pg }}</a>{% endif %} {% endfor %} {% if page.has_next %}<a href="/test_page?page={{ page.next_page_number }}"> 下一页</a> {% else %}<p>下一页</p> {% endif %} </body> </html>
十、生成CSV文件
- csv文件定义:
- 定义:逗号分割值(Comman-Separated values),有时也称为字符分割值,因为分割字符也可以不是逗号,其文件以纯文本形式存储表格数据(数字和文本)
- 说明:可被常见制表工具,如Excel等直接读取
- Python中生成csv文件
- Python提供了内建库 - CSV;可直接通过该库操作csv文件
- 案例如下:
import csv with open('eggs.csv','w',newline='') as csvfile:writer = csv.writer(csvfile) #创建writer对象writer.writerow(['a','b','c']) #以行的方式写入数据,
- django中实现
- 在网站中实现下载csv文件,注意如下
- 响应类型Content-Type类型需要修改为text/csv,这告诉浏览器该文档是csv文件,而不是HTML文件(响应类型在HttpResponse对象中修改)
- 响应会获得一个额外的Content-Disposition 标头,其中包含csv文件的名称,它将被浏览器用于开启"另存为···"对话框
- 案例:
import csv from django.http import HttpResponse from django.shortcuts import render from books.models import Book def make_csv_view(request):response = HttpResponse(content_type = 'text/csv') #修改content-typeresponse['Content-Disposition'] = 'attachment;filename="mybook.csv"'all_book = Book.objects.all()writer = csv.writer(response)writer.writerow(['id','title'])for b in all_book:writer.writerow([b.id,b.book_name])return response
- 在网站中实现下载csv文件,注意如下
十一、用户认证系统
定义:django带有一个用户认证系统,用于处理用户账号、组、权限以及基于cookie的用户会话
用户可直接使用django自带的用户表
详细信息参考官方文档
基本字段:模型位置:
from django.contrib.auth.models import User
Field Type Null Key Default Extra info id int(11) NO PRI NULL auto_increment id password varchar(128) NO NULL 密码 last_login datetime(6) YES NULL 上一次的登陆时间 is_superuser tinyint(1) NO NULL 是否是管理员账号(/admin) username varchar(150) NO UNI NULL 用户名 first_name varchar(150) NO NULL 名 last_name varchar(150) NO NULL 姓 email varchar(254) NO NULL 邮箱 is_staff tinyint(1) NO NULL 是否可以访问admin管理界面 is_active tinyint(1) NO NULL 是否是活跃用户,默认为True,
一般不删除用户,而是将用户的is_active设为Falsedate_joined datetime(6) NO NULL 用户创建的时间 模型操作 - 创建用户
- 创建普通用户
from django.contrib.auth.models import User user = User.objects.create_user(username, email, password) #username、password为必填项
- 创建超级用户
from django.contrib.auth.models import User user = User.objects.create_superuser(username, email, password) #username、password为必填项
- 创建普通用户
模型操作 - 删除用户
- 使用伪删除操作:将is_active的值改为False
from django.contrib.auth.models import User try:user = User.objects.get(username='用户名')user.is_active = Falseuser.save()print("删除普通用户成功") except:print("删除普通用户失败")
- 使用伪删除操作:将is_active的值改为False
模型操作 - 校验密码
- 调用auth中的authenticate
from django.contrib.auth import authenticate user = authenticate(username=username,password=password)
- 说明:如果用户名和密码校验成功则返回对应的user对象,否则返回None
- 调用auth中的authenticate
模型操作 - 修改密码
from django.contrib.auth.models import User try:user = User.objects.get(username='用户名')user.set_password('654321')user.save()return HttpResponse("密码修改成功!") except:return HttpResponse("密码修改失败!")
模型操作 - 登陆状态保持
- 调用auth中的login进行登陆状态保持
from django.contrib.auth import login def login_view(request):user = authenticate(username=username,password=password) #返回校验后的user对象login(request,user) #传入request和校验后的user对象
- 调用auth中的login进行登陆状态保持
模型操作 - 登陆状态校验
- 调用decorators中的login_required,使用装饰器的方法,对需要进行校验的视图添加装饰器
- 如果需要配置登陆校验不成功的跳转页面,则需要在settings.py文件中配置:
LOGIN_URL = '跳转url'
from django.contrib.auth.decorators import login_required @login_required #装饰的视图必须为用户登陆状态下才可访问 def index_view(request):login_user = request.user #获取当前登陆的用户···
模型操作 - 登陆状态取消
from django.contrib.auth import logout def logout_view(request):logout(request) #直接传入request即可
扩展内建用户表的字段
- 方案一:通过建立新表,跟内建表做1对1映射
- 方案二:继承内建的抽象User模型类,重写类属性
- 方案二实现步骤:
- 添加新的应用
- 定义模型类 继承自AbstractUser
- 在settings.py文件中指明AUTH_USER_MODEL = ‘应用名.类名’
- 注意:此操作要在第一次执行migrate之前进行
- 示例:
from django.db import models from django.contrib.auth.models import AbstractUser class UserInfo(AbstractUser): #继承自AbstractUserphone = models.CharField(max_length = 11,default = '') #新添加的字段
#file:settings.py AUTH_USER_MODEL = 'user.UserInfo'
添加用户(与原User使用方法相同)
from user.models import UserInfo UserInfo.onjects.create_user(username='xxx',password='123456',phone='13344445555')
十二、文件上传功能
- 定义:用户可以通过浏览器将图片等文件传至网站
- 场景:用户上传头像、上传流程性的文件【PDF、TXT等】
- 上传规则 - 前端【HTML】
- 文件上传必须为POST提交方式
- 表单
<form>
中文件上传时必须带有enctype="multipart/form-data"
时才会包含文件内容数据 - 表单中用
<input type="file" name = "xxx">
标签上传文件
- 上传规则 - 后端【django】
- 获取上传文件的内容:在视图函数中用
request.FILES
获取文件框的内容 - 语法:file = request.FILES[‘XXX’]
- 说明:
- FILES的key对应页面中的file框的name值
- file:为绑定文件流对象
- file.name:获取文件名
- file.file:为文件的字节流数据
- form表单中的文本框内容仍然是用
request.POST
去取
- 配置文件的访问路径和存储路径
- 在settings.py中设置MEDIA相关配置;django把用户上传的文件统称为media资源
#file:settings.py MEDIA_URL = '/media/' #配置上传文件的访问路径 MEDIA_ROOT = os.path.join(BASE_DIR,'media') #配置文件存储路径
- 手动绑定MEDIA_URL和MEDIA_ROOT
- 在主路由中添加路由
from django.conf import settings from django.conf.urls.static import static urlpatterns += static(settings.MEDIA_URL,document_root = settings.MEDIA_ROOT)
- 说明:使用static将MEDIA_URL与MEDIA_ROOT进行绑定,django接到MEDIA_URL开头的路由请求后,去MEDIA_ROOT路径查找资源
- 在settings.py中设置MEDIA相关配置;django把用户上传的文件统称为media资源
- 文件写入方案1:传统的open方式
@csrf_exmpt #取消csrf对以下视图函数的验证 def upload_view(request):if request.method == 'GET':return render(request,'test_upload.html')elif request.methof == 'POST':a_file = request.FILES['myfile'] #获取上传文件的文件流对象print("上传的文件名是:",a_file.name)filename = os.path.join(settings.MEDIA_ROOT,a_file.name) #j将上传的文件名与文件存储路径进行拼接with open(filename,'w') as f:data = a_file.file.read() #使用read()方法将a_file.file字节流数据读取出来f.write(data) #写入读取出来的数据return HttpResponse("接收文件:",a_file.name + "成功")
- 文件写入方案2:diango实现 - 借助ORM
- 用法:借助模型类中FileField(upload=‘子目录名’) ; 该字段用于存储文件的相对路径,如果文件名已存在,django会自动为文件重命名
@csrf_exempt def upload_view_dj(request):if request.method == 'GET':return render(request,'test_upload.html')elif request.method == 'POST':title = request.POST['title']a_file = request.FILES['myfile']Content.objects.create(desc=title,myfile=a_file) #desc、myfile为Content模型类中的字段名return HttpResponse("----upload is ok----")
- 用法:借助模型类中FileField(upload=‘子目录名’) ; 该字段用于存储文件的相对路径,如果文件名已存在,django会自动为文件重命名
- 获取上传文件的内容:在视图函数中用
- 上传示例:
# 1.file:settings.py , 配置MEDIA_URL和MEDIA_ROOT MEDIA_URL = '/media/' MEDIA_ROOT = os.path.join(BASE_DIR,'media') #在项目根目录下创建media 文件夹用于存放上传的文件 # 2.file:urls.py , 绑定上传文件的路由与存放目录 from django.conf.urls.static import static from django.urls import path from test_upload import views from django.conf import settings urlpatterns = [path('test_upload',views.upload_view) ] urlpatterns += static(settings.MEDIA_URL,document_root = settings.MEDIA_ROOT) # 3.file:models.py , 创建模型类 from django.db import models class test_upload(models.Model):title = models.CharField(verbose_name='标题',max_length=20)mfile = models.FileField(verbose_name='media') # 4.file:views.py , 创建视图函数 from django.http import HttpResponse from django.shortcuts import render from django.views.decorators.csrf import csrf_exempt from .models import test_upload @csrf_exempt def upload_view(request):if request.method == 'GET':return render(request,'test_upload.html')elif request.method == 'POST':title = request.POST['title']m_file = request.FILES['myfile']test_upload.objects.create(title=title,mfile=m_file)return HttpResponse('----文件上传成功----')
<!--5. file:test_upload.html , 配置模板文件--> <!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>文件上传</title> </head> <body> <form action="test_upload" method="post" enctype="multipart/form-data"><input type="text" name="title"><input type="submit" value="提交"><input type="file" name="myfile"> </form> </body> </html>
十三、发送邮件
- 业务场景
- 业务警告(捕捉异常可用traceback.format_exc())、邮箱验证、密码找回
- 邮件相关协议
- SMTP
- 全称是“simple mail transfer protocol”,即简单邮件传输协议(25号端口)
- 功能:它是一组用于从源地址到目的地址传输邮件的规范,通过它来控制邮件的中转 -> 属于推送协议
- IMAP
- 全称是“Internet mail Access protocol”,即交互式邮件访问协议,是一个应用层协议(143端口)
- 功能:用来从本地邮件客户端(Outlook express、Foxmail等)访问远程服务器上的邮件 -> 属于拉取协议
- POP3
- 全称是“Post office protocol 3”,即邮局协议的第三版本,是TCP/IP协议族中的一员(110端口)
- 功能:主要用于支持使用客户端远程管理在服务器上的电子邮件 -> 属于拉取协议
- IMAP VS POP3:两者均为拉取型协议,负责从邮件服务器中下载邮件
- IMAP 具备摘要浏览功能,可预览部分摘要,再下载整个邮件
- IMAP 为双向协议,客户端操作可反馈给服务器
- POP3 必须下载全部邮件,无摘要功能
- POP3 为单向协议,客户端操作无法同步到服务器
- SMTP
- django发送邮件
- django中配置邮件功能,主要为SMTP协议,负责发送邮件
- 原理:
- 给django授权一个邮箱
- django用该邮箱给对应收件人发送邮件
- django.core.mail封装了电子邮件的自动发送SMTP协议
- 授权步骤:
- 登陆用于发送邮件的邮箱
- 获取用于发送邮件的邮箱授权码(IMAP/SMTP、POP3/SMTP均可)
- django中配置:
#file:settings.py 发送邮件相关配置 EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' # 发送邮件的引擎 EMAIL_HOST = 'smtp.qq.com' # 发送邮件的SMTP服务器地址 EMAIL_PORT = 25 #SMTP服务的端口号 EMAIL_HOST_USER = 'XXXXXXXX@QQ.COM' #发送邮件的邮箱账号 EAMIL_HOST_PASSWORD = '********' #第三方登录邮箱的授权码 EMAIL_USE_TLS = False #与SMTP服务器通信时,是否启动TLS链接(安全链接)默认为False
- 函数调用
from django.core import mail mail.send_mail(subject, #邮件主题message, #邮件内容from_email, #发送人【当前配置邮箱】recipient_list=['xxxxx@qq.com','xxxxx@163.com'],#邮件接收人列表)
十四、项目部署
- 基础概念:项目部署是指在软件开发完毕后,将开发机器上运行的软件实际安装到服务器上进行长期运行
- 部署步骤
- 在安装机器上安装和配置同版本的环境(Python、数据库等)
- django项目迁移
sudo scp /home/path1········/mysite1 root@xx.xx.xx.xx:/home/root/path2
path1 为本地项目路径,path2为迁移到服务器中存放的路径 - 用uWSGI替代python manage.py runserver方法启动服务器
- 配置nginx反向代理服务器
- 用nginx配置静态文件路径,解决静态文件路径问题
- uWSGI
定义:是WSGI中的一种,它实现了http协议、WSGI协议以及uwsgi协议,uWSGI功能完善,支持协议众多,主要以学习配置为主
uWSGI安装:ubuntu执行 sudo pip install uwsgi==2.0.18 -i https://pypi.tuna.tsinghua.edu.cn/simple/
- 检查是否安装成功:
sudo pip freeze|grep -i 'uwsgi'
如果成功安装则会显示对应的版本
- 检查是否安装成功:
配置uWSGI
- 添加配置文件:项目同名文件夹/uwsgi.ini;
mysite1/mysite1/uwsgi.ini
;正式项目的时候一般将这个配置文件命名为跟项目同名.ini#file:uwsgi.ini [uwsgi] #文件开头第一行必须写这个 socket = 127.0.0.1:8000 #套接字方式的IP地址:端口号,此模式必需要有nginx http = 127.0.0.1:8000 #http通信方式的IP地址:端口号 chdir = /home/···/my_project #项目当前工作目录 wsgi-file = my_project/wsgi.py #项目中wsgi.py文件的目录,相对于当前工作目录;my_project是项目目录下的同名文件夹 process = 4 #进程数 threads = 2 #每个进程的线程个数 pidfile = uwsgi.pid #pid->主进程的ID,uwsgi启动后会pid写进一个文件里,pidfile用于指明文件的位置,启动后会在启动目录下生成一个uwsgi.pid文件 daemonize = uwsgi.log #服务日志文件的位置,以后台启动,并且将日志文件输出到启动目录下的uwsgi.log中 master = True #开启主进城管理模式
- 特殊说明:django中的settings.py需做如下配置
- 修改DEBUG = True 改为DEBUG = False #避免将报错信息输出给用户
- 将ALLOWED_HOST = []改为ALLOWED_HOSTS = [‘公网域名’] 或者 [‘服务监听的IP地址’]
- 添加配置文件:项目同名文件夹/uwsgi.ini;
启动uwsgi:切换到uWSGI配置文件所在目录执行
uwsgi --ini uwsgi.ini
‘–ini’ 为初始化uwsgi停止uwsgi:切换到uWSGI配置文件所在目录执行
uwsgi --stop uwsgi.pid
查看是否启动成功:
ps aux|grep 'uwsgi'
查看是否有进程启动,无论是启动还是关闭,都需要执行此命令确认是否符合预期注意:启动成功后,进程在后台执行,所有日志均输出在配置文件所在目录的uwsgi.log中;Django中代码有任何修改,均需要重新启动uwsgi
uWSGI常见问题汇总
问题 原因 解决方案 启动失败 端口被占用,其它进程占用了uWSGI启动的端口 执行 sudo lsof -i:端口号
查询出具体进程;执行sudo kill -9 端口号
,杀掉进程,重新启动uWSGI即可停止失败 重复启动uWSGI导致pid文件中的进程号失准 ps 出uWSGI进程,手动kill掉
- Nginx
定义:nginx是轻量级的高性能web服务器,提供了诸如http代理和反向代理、负载均衡等一系列重要特性;采用c语言编写,执行效率高
作用:
- 负载均衡,多台服务器轮流处理请求
- 反向代理:nginx反向代理可以以uwsgi协议、http协议转发给uWSGI
- 正向代理与反向代理介绍
- 正向代理:代理的对象是客户端,隐藏了真实的客户端,服务端不知道真实的客户端是谁
- 反向代理:代理的对象时服务端,隐藏了真实的服务端,客户端不知道真实的服务端是谁
- 负载均衡:nginx将请求任务平衡、分摊到多个服务器上进行处理
- 正向代理与反向代理介绍
原理:客户端请求nginx,再由nginx将请求转发uWSGI运行的django
安装:
sudo apt install nginx
(更换国内源 :vim /etc/apt/sources.list 更改国内源 sudo apt-get update
) -> 安装完毕后在ubuntu终端中输入nginx -v
会显示nginx相应的版本号;安装完毕后nginx会自动启动,并占用80端口配置:修改nginx的配置文件:
/etc/nginx/sites-enabled/default
;sudo vim default
server{location / {# First attempt to serve request as file, then# as directory, then fall back to displaying a 404.# try_files $uri $uri/ =404; # nginx可以通过请求路由自动去查找html文件,查找目录默认为: root /var/www/html,如果未找到会报404错误uwsgi_pass 127.0.0.1:8000; #所有用‘/’开头的路由都重定向到127.0.0.1的8000端口,include /etc/nginx/uwsgi_params; #如果要使用uwsgi协议就必须将所有的参数转到uwsgi下}}
启动/停止 nginx
sudo /etc/init.d/nginx/ start|stop|restart|status
或sudo service nginx start|stop|restart|status
- start:启动、stop:停止、restart:重启
- nginx配置只要修改,就需要进行重启,否则配置不生效
uWSGI修改配置:nginx负责接收请求,并把请求转发给后面的uWSGI,此模式下,uWSGI需要以socket模式启动;
#file:uwsgi.ini [uwsgi] #文件开头第一行必须写这个 socket = 127.0.0.1:8000 #套接字方式的IP地址:端口号,此模式必需要有nginx # http = 127.0.0.1:8000 #http通信方式的IP地址:端口号
常见问题排查:
- 查日志:
- nginx 日志位置:
- 异常日志:/var/log/nginx/error.log
- 正常访问信息:/var/log/nginx/access.log
- uwsgi 日志位置:项目同名目录下/uwsgi.log
- nginx 日志位置:
- 访问127.0.0.1:80地址,返回502响应;分析
502响应代表nginx反向代理配置成功,但是对应的uWSGI未启动
- 访问127.0.0.1:80/url,返回404响应:1. 路由的确不在django配置中;2. nginx配置错误,未禁止掉try_files
- 查日志:
静态文件配置
- 原因:使用nginx反向代理后,django不会再进行静态文件管理,
- 创建新路径用于存放django的所有静态文件
/home/···/项目名_static/
- 在django的settings.py中添加新配置
#file:settings.py #配置后,django在收集静态文件时会在项目名_static文件夹下自动新建static文件夹用于收集静态文件 STATIC_ROOT = '/home/···/项目名_static/static'
- 进入项目根目录,执行
python manage.py collectstatic
执行该命令后,django会将项目中的所有静态文件收集到STATIC_ROOT中,包括django内建的静态文件- nginx配置中添加新配置
#file:/etc/nginx/site-enabled/default # 添加localtion /static 路由配置,重定向到静态文件存放目录即可 location /static {root /home/···/day04_static;}
- nginx配置中添加新配置
- 自定义404/500页面
- 在模板文件夹内添加404.html模板,当视图触发Http404异常时将会自动显示;404.html仅在发布版中(即settings.py中的DEBUG=False时)才起作用,当向应处理函数触发Http404异常时就会跳转到404界面
- 邮箱报警配置
- 作用:当正式服务器上代码运行有报错时,可将错误追溯信息发送至指定的邮箱
- 在settings.py中 - 在基础邮箱授权后,添加如下配置
DEBUG = False #关闭调试模式 ADMINS = [('收件人姓名','收件人邮箱'),('收件人2姓名','收件人2邮箱')] #错误报告接收方 SERVER_EMAIL = 'email配置中的邮箱' #发送错误报告邮件发件人,默认为root@localhost账户,多数邮件服务器会拒绝此邮件
- 过滤敏感信息:报错邮件中会显示一些错误的追踪,这些错误追踪会出现如password等敏感信息,django已经将配置文件中的敏感信息过滤修改为多个星号,但是用户自定义的视图函数需要用户手动过滤敏感信息;可过滤如下信息(局部变量、POST提交数据)
- 过滤局部变量
from django.views.decorators.debug import sensitive_variables @sensitive_variables('user','pw','cc') #使用装饰器的方式,将需要过滤的字段作为参数传递给装饰器 def process_info(user):pw = user.passwordcc = user.credit_card_numbername = user.name
- 说明
- 若报错邮件中牵扯到user,pw,cc,等局部变量的值,则会将其替换称******,而name变量还显示其真实值
- 多个装饰器时,需要将其放在最顶部
- 若不传参数,则过滤所有局部变量的值
- 过滤POST数据
from django.views.decorators.debug import sensitive_post_parameters @sensitive_post_parameters('password','username') def index(request):s = request.POST['username'] + request.POST['abcd']# abcd并不存在,此时引发error#POST中username及password的值会被替换称 ****
Django学习之路-基础篇相关推荐
- JAVA学习之路--基础篇三
目录 关于Java中从键盘输入的语句 nextxxx().next().nextLine()的区别 语句 if和if else语句 Switch语句 for语句 while和do..while bre ...
- python学习之路基础篇(第四篇)
一.课程内容回顾 1.python基础 2.基本数据类型 (str|list|dict|tuple) 3.将字符串"老男人"转换成utf-8 s = "老男人" ...
- python学习之路基础篇(第八篇)
一.作业(对象的封装) 要点分析 1.封装,对象中嵌套对象 2.pickle,load,切记,一定要先导入相关的类二.上节内容回顾和补充 面向对象基本知识: 1.类和对象的关系 2.三大特性: 封装 ...
- Python学习之路基础篇--10Python基础,函数进阶
1 命名空间 对于Python 来说命名空间一共有三种 1 内置命名空间 -- Python 解释器 就是Python 解释器一启动就可以使用的名字,储存在内置命名空间中.内置的名字在启动解释器的时候 ...
- 大数据学习之路 JUC篇
大数据学习之路 JUC篇(1) 前提说明 本人是一名学生,茫茫it行业的一名卑微的小白,这是我第一次写博客.其原因是学着学着知识发现回顾的时候差不多全忘记了!!为了总结.复习自己以往学到过的有关大数据 ...
- django学习之路(五)站点管理admin - django - 一直很安静 - Powered by Discuz!
django学习之路(五)站点管理admin - django - 一直很安静 - Powered by Discuz!: "" (Via.) 转载于:https://www.cn ...
- Python学习日记-day1基础篇 字符 输出 注释
Python学习日记-day1基础篇 字符 输出 注释 by北栀一刺 # -*- coding: utf-8 -*- """ Spyder EditorThis is a ...
- Java学习---Day16_IO流基础篇
Java学习-Day16_IO流基础篇 文件操作 操作磁盘上的某一个文件或某一个文件夹,可以对他们进行创建或删除.移动.属性获取.属性设置等操作.但是,不包含读取文件的内容.拷贝文件 ps:java中 ...
- PHP学习文档——基础篇
PHP学习文档--基础篇 PHP学习文档--基础篇 标记 短标记 脚本标记 标准标记(常用) PHP注释 行注释 块注释 PHP语句分隔符 变量 变量命名规则 预定义变量 可变变量 变量传值 常量 定 ...
最新文章
- Golang实现requests库
- linux系统调用挂钩方法总结
- tomcat出现5个using_出现急性心梗,要当心5个并发症,一个都不好惹!
- 【小米面试题】-给定一个具体时间,计算时针与分针之间的角度
- ArcGIS快速裁剪栅格数据
- boost::program_options模块一个配置ini文件被解析程序选项库,它包括许多不同的值类型的测试程序
- Hadoop -- ES -- CURD
- 【玩转cocos2d-x之八】精灵类CCSprite
- ajax 与route的区别,浅析vue-router jquery和params传参(接收参数)$router $route的区别
- 全国计算机等级考试题库二级C操作题100套(第20套)
- Spark精华问答 | spark性能优化方法
- 2月第3周国内域名商TOP10:爱名网排名升至第八
- 《乐者为王——自由软件Linux之父李纳斯·托沃兹自述》
- Java中打印程序执行的开始时间和结束时间
- VC2010 中 MFC 的改进导致 XTP 停靠窗体无法显示的解决方法
- MacOS Ventura 13.0 (22A380) 正式版带 OC 0.8.5 and winPE 双分区原版黑苹果镜像
- CISA 考试大纲及考点
- 没想到cnblog也有月经贴,其实C#值不值钱不重要。
- 如何将这个递归调用停下来呢?
- LR(逻辑回归)介绍文档