Django完整版概述
WEB框架本质:
服务器程序和应用程序:
是一个socket服务端,而用户的浏览器就是一个socket客户端。例子:import socketsok = socket.socket()sok.bind(("127.0.0.1",8008))sok.listen()while True: conn,addr = sok.accept() conn.recv(1000) conn.send(b"200\tOK") conn,close()
服务器程序:负责对socket服务端进行封装,并在请求到来时,对请求的各种数据进行整理。应用程序:负责具体的逻辑处理。
1.WSGI:
WSGI(Web Server GatewayInterface)一种规范,它定义了使用Python编写的web应用程序与web服务器程序之间的接口格式,实现web应用程序与web服务器程序间的解耦。
2.uwsgi
Nginx中HttpUwsgiModule的作用是与uWSGI服务器进行交换。WSGI是一种Web服务器网关接口。它是一个Web服务器(如nginx,uWSGI等服务器)与web应用(如用Flask框架写的程序)通信的一种规范。
3.uWSGI
uWSGI是一个Web服务器,它实现了WSGI协议、uwsgi、http等协议。
详细内容(https://www.jianshu.com/p/679dee0a4193)
4.WSGI / uwsgi / uWSGI 这三个概念的区分。
- WSGI是一种通信协议。- uwsgi是一种线路协议而不是通信协议,在此常用于在uWSGI服务器与其他网络服务器的数据通信。- 而uWSGI是实现了uwsgi和WSGI两种协议的Web服务器
MVC和MTV框架:
MVC:
Web应用分为模型(M),控制器(C)视图(V),他们之间以一种插件式的、松耦合的方式连接在一起,模型负责业务对象与数据库的映射(ORM),视图负责与用户的交互(页面),控制器接受用户的输入调用模型和视图完成用户的请求。
![](/assets/blank.gif)
MTV:
M 代表模型(Model): 负责业务对象和数据库的关系映射(ORM)。T 代表模板 (Template):负责如何把页面展示给用户(html)。V 代表视图(View): 负责业务逻辑,并在适当时候调用Model和Template。
![](/assets/blank.gif)
Django总览(django2.0)
1.Django路由系统url.py:
说明:它的本质是URL与要为该URL调用的视图函数之间的映射表。你就是以这种方式告诉Django,对于这个URL调用这段代码,对于那个URL调用那段代码。
from django.urls import path,re_pathurlpatterns = [ path(正则表达式, views视图函数,参数,别名), path('articles/2003/', views.special_case_2003), #re_path('^articles/2003/$',view.special_case_2003), path('articles/<int:year>/<int:month>/<slug:slug>/', views.article_detail), #re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2})/$', ,views.article_detail),]
补充:Django settings.py配置文件中默认没有 APPEND_SLASH 这个参数,但 Django 默认这个参数为 APPEND_SLASH = True。 其作用就是自动在网址结尾加'/'
路由分发include:
----------------------main/urls.py----------------------from django.urls import path,re_path,includeurlpatterns = [ path('',include('app01.urls'))]----------------------app/urls.py----------------------from django.contrib import adminfrom django.urls import path,re_pathfrom app01 import viewsurlpatterns = [ path('admin/', admin.site.urls), path('book/<int:id>/',views.book), re_path('^index/$', views.index),]
2.Django视图函数views.py:
说明:一个视图函数(类),简称视图,是一个简单的Python 函数(类),它接受Web请求并且返回Web响应。响应可以是一张网页的HTML内容,一个重定向,一个404错误,一个XML文档,或者一张图片。无论视图本身包含什么逻辑,都要返回响应。代码写在哪里也无所谓,只要它在你当前项目目录下面。除此之外没有更多的要求了——可以说“没有什么神奇的地方”。为了将代码放在某处,大家约定成俗将视图放置在项目(project)或应用程序(app)目录中的名为views.py的文件中。
FBV:
在视图view中使用函数处理请求:def index(request): if request.method == 'GET': return HttpResponse('this is index') if request.method =='POST': return HttpResponse('this is post index')
CBV:
在视图view中使用类处理请求:优点:可以用不同的函数针对不同的HTTP方法处理,而不是通过很多if判断,提高代码可读性-------------------views.py---------------------------from django.views import Viewclass Index(View): def get(self,request,*args, **kwargs): return HttpResponse('this is index') def post(self,request,*args, **kwargs): return HttpResponse('this is post index')----------------urls.py-------------------------------- path('index/', views.Index.as_view()),
视图加装饰器:例如登录校验,只有登录才能查看的页面。
-------------------------------函数----------------------------def warapper(func): def inner(*args,**kwargs): start = time.time() ret = func(*args,**kwargs) end = time.time() print(end-start) return ret return inner
@warapperdef index(request): if request.method == 'GET': return HttpResponse('this is index') if request.method =='POST': return HttpResponse('this is post index') --------------------------------类-------------------------------
前提:类中的方法与独立函数不完全相同,因此不能直接将函数装饰器应用于类中的方法 ,我们需要先将其转换为方法装饰器。Django中提供了method_decorator装饰器用于将函数装饰器转换为方法装饰器。对于CBV添加装饰器:///
1.直接添加在dispatch里面,这样每个函数都会执行from django.utils.decorators import method_decorator@method_decorator(login_test)def dispatch(self, request, *args, **kwargs): res = super(IndexView, self).dispatch(request, *args, **kwargs) return res
2.添加在每一个函数中from django.utils.decorators import method_decorator@method_decorator(login_test)def get(self, request, *args, **kwargs): return HttpResponse('this is index')
3.直接添加在类上,后面的name表示只给get添加装饰器from django.utils.decorators import method_decorator@method_decorator(login_test, name='get') get是给get方法加 (以这种方式如果想给多个方法加装饰器,需要写多层装饰器,因为name这个参数的值必须是个字符串,并且不能同时写两个方法) @method_decorator(login_test, name='post') post是给post方法加 class Index(View): def get(self,request): return HttpResponse('this is post index')
csrf-token装饰器:
注意csrf-token装饰器的特殊性,在CBV模式下它只能加在dispatch上面 下面这是csrf_token的装饰器: @csrf_protect,为当前函数强制设置防跨站请求伪造功能,即便settings中没有设置csrfToken全局中间件。 @csrf_exempt,取消当前函数防跨站请求伪造功能,即便settings中设置了全局中间件。
注意:from django.views.decorators.csrf import csrf_exempt,csrf_protec
request对象:
概述:当一个页面被请求时,Django就会创建一个包含本次请求原信息的HttpRequest对象。Django会将这个对象自动传递给响应的视图函数,一般视图函数约定俗成地使用 request 参数承接这个对象。
1.属性:
请求相关的常用值: path_info 返回用户访问url,不包括域名 method 请求中使用的HTTP方法的字符串表示,全大写表示。 GET 包含所有HTTP GET参数的类字典对象 POST 包含所有HTTP POST参数的类字典对象 body 请求体,byte类型 request.POST的数据就是从body里面提取到的属性,所有的 属性应该被认为是只读的,除非另有说明。----------------------------------------------------------------------------------属性:django将请求报文中的请求行、头部信息、内容主体封装成 HttpRequest 类中的属性。除了特殊说明的之外,其他均为只读的。
0.HttpRequest.scheme 表示请求方案的字符串(通常为http或https)
1.HttpRequest.body 一个字符串,代表请求报文的主体。在处理非 HTTP 形式的报文时非常有用,例如:二进制图片、XML,Json等。但是,如果要处理表单数据的时候,推荐还是使用 HttpRequest.POST 。另外,我们还可以用 python 的类文件方法去操作它,详情参考 HttpRequest.read() 。 2.HttpRequest.path 一个字符串,表示请求的路径组件(不含域名)。 例如:"/music/bands/the_beatles/" 3.HttpRequest.method 一个字符串,表示请求使用的HTTP 方法。必须使用大写。 例如:"GET"、"POST" 4.HttpRequest.encoding 一个字符串,表示提交的数据的编码方式(如果为 None 则表示使用 DEFAULT_CHARSET 的设置,默认为 'utf-8')。这个属性是可写的,你可以修改它来修改访问表单数据使用的编码。接下来对属性的任何访问(例如从 GET 或 POST 中读取数据)将使用新的 encoding 值。如果你知道表单数据的编码不是 DEFAULT_CHARSET ,则使用它。 5.HttpRequest.GET 一个类似于字典的对象,包含 HTTP GET 的所有参数。 6.HttpRequest.POST 一个类似于字典的对象,如果请求中包含表单数据,则将这些数据封装成 QueryDict 对象。如果使用 POST上传文件的话,文件信息将包含在 FILES 属性中。 7.HttpRequest.COOKIES 一个标准的Python 字典,包含所有的cookie。键和值都为字符串。 8.HttpRequest.FILES 一个类似于字典的对象,包含所有的上传文件信息。FILES 中的每个键为<input type="file" name="" /> 中的name,值则为对应的数据。注意,FILES 只有在请求的方法为POST 且提交的<form> 带有enctype="multipart/form-data" 的情况下才会包含数据。否则,FILES 将为一个空的类似于字典的对象。 例如: def upload(request): """ 保存上传文件前,数据需要存放在某个位置。默认当上传文件小于2.5M时,django会将上传文件的全部内容读进内存。从内存读取一次,写磁盘一次。 但当上传文件很大时,django会把上传文件写到临时文件中,然后存放到系统临时文件夹中。 :param request: :return: """ if request.method == "POST": # 从请求的FILES中获取上传文件的文件名,file为页面上type=files类型input的name属性值 filename = request.FILES["file"].name # 在项目目录下新建一个文件 with open(filename, "wb") as f: # 从上传的文件对象中一点一点读 for chunk in request.FILES["file"].chunks(): # 写入本地文件 f.write(chunk) return HttpResponse("上传OK")
9.HttpRequest.META 一个标准的Python 字典,包含所有的HTTP 首部(请求头信息)。具体的头部信息取决于客户端和服务器,下面是一些示例: CONTENT_LENGTH —— 请求的正文的长度(是一个字符串)。 CONTENT_TYPE —— 请求的正文的MIME 类型。 HTTP_ACCEPT —— 响应可接收的Content-Type。 HTTP_ACCEPT_ENCODING —— 响应可接收的编码。 HTTP_ACCEPT_LANGUAGE —— 响应可接收的语言。 HTTP_HOST —— 客服端发送的HTTP Host 头部。 HTTP_REFERER —— Referring 页面。 HTTP_USER_AGENT —— 客户端的user-agent 字符串。 QUERY_STRING —— 单个字符串形式的查询字符串(未解析过的形式)。 REMOTE_ADDR —— 客户端的IP 地址。 REMOTE_HOST —— 客户端的主机名。 REMOTE_USER —— 服务器认证后的用户。 REQUEST_METHOD —— 一个字符串,例如"GET" 或"POST"。 SERVER_NAME —— 服务器的主机名。 SERVER_PORT —— 服务器的端口(是一个字符串)。 从上面可以看到,除 CONTENT_LENGTH 和 CONTENT_TYPE 之外,请求中的任何 HTTP 首部转换为 META 的键时,都会将所有字母大写并将连接符替换为下划线最后加上 HTTP_ 前缀。所以,一个叫做 X-Bender 的头部将转换成 META 中的 HTTP_X_BENDER 键。
10.HttpRequest.user 一个 AUTH_USER_MODEL 类型的对象,表示当前登录的用户。如果用户当前没有登录,user 将设置为 django.contrib.auth.models.AnonymousUser 的一个实例。你可以通过 is_authenticated() 区分它们。 例如: if request.user.is_authenticated(): # Do something for logged-in users. else: # Do something for anonymous users.
user 只有当Django 启用 AuthenticationMiddleware 中间件时才可用。------------------------------------------------------------------------------------
匿名用户 class models.AnonymousUser django.contrib.auth.models.AnonymousUser 类实现了django.contrib.auth.models.User 接口,但具有下面几个不同点: id 永远为None。 username 永远为空字符串。 get_username() 永远返回空字符串。 is_staff 和 is_superuser 永远为False。 is_active 永远为 False。 groups 和 user_permissions 永远为空。 is_anonymous() 返回True 而不是False。 is_authenticated() 返回False 而不是True。 set_password()、check_password()、save() 和delete() 引发 NotImplementedError。 New in Django 1.8: 新增 AnonymousUser.get_username() 以更好地模拟 django.contrib.auth.models.User。
11.HttpRequest.session 一个既可读又可写的类似于字典的对象,表示当前的会话。只有当Django 启用会话的支持时才可用。 完整的细节参见会话的文档。
2.方法
1.HttpRequest.get_full_path() 返回 path,如果可以将加上查询字符串。 例如:"/music/bands/the_beatles/?print=true"2.HttpRequest.get_signed_cookie(key, default=RAISE_ERROR, salt='', max_age=None) 返回签名过的Cookie 对应的值,如果签名不再合法则返回django.core.signing.BadSignature。 如果提供 default 参数,将不会引发异常并返回 default 的值。 可选参数salt 可以用来对安全密钥强力攻击提供额外的保护。max_age 参数用于检查Cookie 对应的时间戳以确保Cookie 的时间不会超过max_age 秒。3.HttpRequest.is_secure() 如果请求时是安全的,则返回True;即请求通是过 HTTPS 发起的。4.HttpRequest.is_ajax() 如果请求是通过XMLHttpRequest 发起的,则返回True,方法是检查 HTTP_X_REQUESTED_WITH 相应的首部是否是字符串'XMLHttpRequest'。大部分现代的 JavaScript 库都会发送这个头部。如果你编写自己的 XMLHttpRequest 调用(在浏览器端),你必须手工设置这个值来让 is_ajax() 可以工作。如果一个响应需要根据请求是否是通过AJAX 发起的,并且你正在使用某种形式的缓存例如Django 的 cache middleware,你应该使用 vary_on_headers('HTTP_X_REQUESTED_WITH') 装饰你的视图以让响应能够正确地缓存。
3.补充:
from django.shortcuts import render,HttpResponse,redirect
# Create your views here.
def index(request): print(request.method) #请求方式 print(request.path) #请求路径,不带参数的 print(request.POST) #post请求数据 字典格式 print(request.GET) #get的请求数据 字典格式 print(request.META) #请求头信息,将来用到哪个咱们再说哪个 print(request.get_full_path()) #获取请求路径带参数的,/index/?a=1 print(request.is_ajax()) #判断是不是ajax发送的请求,True和False ''' Django一定最后会响应一个HttpResponse的示例对象 三种形式: 1 HttpResponse('字符串') 最简单 2 render(页面) 最重要 2.1 两个功能 -- 读取文件字符串 -- 嵌入变量(模板渲染) html里面:{{ name }} , {'name':'chao'}作为render的第三个参数,想写多个变量{'name':'chao','hobby':['篮球','羽毛球']....} 3 redirect() 重定向 最难理解,某个网站搬家了,网址变了,访问原来的网址就重定向到一个新网址,就叫做重定向,网站自己做的重定向,你访问还是访问的你之前的,你自己啥也不用做,浏览器发送请求,然后服务端响应,然后服务端告诉浏览器,你直接跳转到另外一个网址上,那么浏览器又自动发送了另外一个请求,发送到服务端,服务端返回一个页面,包含两次请求,登陆成功后跳转到网站的首页,网站首页的网址和你login登陆页面的网址是不用的。
''' return render(request,'index.html',{'name':'chao'}) # return HttpResponse('ok')
注意:键值对的值是多个的时候,比如checkbox类型的input标签,select标签,需要用:request.POST.getlist("hobby")
响应对象:
HttpResponse对象
说明:由Django自动创建的HttpRequest对象相比,HttpResponse对象是我们的职责范围了。我们写的每个视图都需要实例化,填充和返回一个HttpResponse。
使用: 传递字符串from django.http import HttpResponseresponse = HttpResponse("Here's the text of the Web page.")response = HttpResponse("Text only, please.", content_type="text/plain") 设置或删除响应头信息response = HttpResponse()response['Content-Type'] = 'text/html; charset=UTF-8'del response['Content-Type'] --------------------------------------------------------------------------------- 属性: HttpResponse.content:响应内容 HttpResponse.charset:响应内容的编码 HttpResponse.status_code:响应的状态码 ----------------------------------------------------------------------------------
JsonResponse对象
说明:JsonResponse是HttpResponse的子类,专门用来生成JSON编码的响应。
区别:
JsonResponse默认就是content_type="application/json"。HttpResponse(json.dumps(data),content_type="application/json")
from django.http import JsonResponsedef index(request): # 以1方式 return JsonResponse({'name': 'jbar'}) # 则前端接收 console.log(data.msg); # 以2方式 return HrrpResponse(json.dumps({'name': 'jbar'})) # 则前端接收 var data = json.parse(data) console.log(data.name);
render对象
说明:结合一个给定的模板和一个给定的上下文字典,并返回一个渲染后的 HttpResponse 对象
render(request,template_name,context=None,context_type=None,status=None,using=None)参数: request: 用于生成响应的请求对象。 template_name:要使用的模板的完整名称,可选的参数 context:添加到模板上下文的一个字典。默认是一个空字典。如果字典中的某个值是可调用的,视 图将在渲染模板之前调用它。 content_type:生成的文档要使用的MIME类型。默认为 DEFAULT_CONTENT_TYPE 设置的值。默认 为'text/html' status:响应的状态码。默认为200。 useing: 用于加载模板的模板引擎的名称。 from django.shortcuts import render---------------------------------------------------------------------------------例如:def index(request): return render(request, 'myapp/index.html', {'foo': 'bar'})
redirect对象
说明:重定向到某个url,在对重定向的url发送一个请求
1.一个定义的路由urldef index(request): return redict('/home/')2.一个完整的网址def index(request): return redict('http://www.baidu.com')
关于重定向的补充说明:
1.301和302的区别。 301和302状态码都表示重定向,就是说浏览器在拿到服务器返回的这个状态码后会自动跳转到一个新的URL地址,这个地址可以从响应的Location首部中获取 (用户看到的效果就是他输入的地址A瞬间变成了另一个地址B)——这是它们的共同点。 他们的不同在于。301表示旧地址A的资源已经被永久地移除了(这个资源不可访问了),搜索引擎在抓取新内容的同时也将旧的网址交换为重定向之后的网址; 302表示旧地址A的资源还在(仍然可以访问),这个重定向只是临时地从旧地址A跳转到地址B,搜索引擎会抓取新的内容而保存旧的网址。 SEO302好于3012.重定向原因:(1)网站调整(如改变网页目录结构);(2)网页被移到一个新地址;(3)网页扩展名改变(如应用需要把.php改成.Html或.shtml)。 这种情况下,如果不做重定向,则用户收藏夹或搜索引擎数据库中旧地址只能让访问客户得到一个404页面错误信息,访问流量白白丧失;再者某些注册了多个域名的 网站,也需要通过重定向让访问这些域名的用户自动跳转到主站点等。复制代码临时重定向(响应状态码:302)和永久重定向(响应状态码:301)对普通用户来说是没什么区别的,它主要面向的是搜索引擎的机器人。
3.Django模板系统
介绍:
关于模板渲染你只需要记两种特殊符号(语法):{{ 变量 }}和 {% 逻辑语句 %}变量相关的用{{}},逻辑相关的用{%%}。
过滤器:
作用:使用过滤器来改变变量的显示。过滤器的语法: {{ value|filter_name:参数 }} 使用管道符"|"来应用过滤器。 例如: {{ name|lower }}会将name变量应用lower过滤器之后再显示它的值。lower在这里的作用是将文本全都变成小写。-----------------------------------------------------------------------------------1.default 如果一个变量是false或者为空,使用给定的默认值。 否则,使用变量的值。{{ value|default:"nothing"}}如果value没有传值或者值为空的话就显示nothing2.length 返回值的长度,作用于字符串和列表。{{ value|length }}.返回value的长度,如 value=['a', 'b', 'c', 'd']的话,就显示4.3.filesizeformat 将值格式化为一个 “人类可读的” 文件尺寸 (例如 '13 KB', '4.1 MB', '102 bytes', 等等)。例如:{{ value|filesizeformat }}。如果 value 是 123456789,输出将会是 117.7 MB。4.slice 切片,如果 value="hello world",还有其他可切片的数据类型{{value|slice:"2:-1"}}5。date 格式化,如果 value=datetime.datetime.now(){{ value|date:"Y-m-d H:i:s"}}关于时间日期的可用的参数(除了Y,m,d等等)还有很多,有兴趣的可以去查查看看。详细:https://www.cnblogs.com/clschao/articles/10414811.html
标签:
for标签
1.遍历一个列表:{% for person in person_list %} <p>{{ person.name }}</p>{% endfor %} 2.遍历一个字典:{% for key,val in dic.items %} <p>{{ key }}:{{ val }}</p>
if标签
说明:if语句支持 and 、or、==、>、<、!=、<=、>=、in、not in、is、is not判断,注意条件两边都有空格。{% if num > 100 or num < 0 %} <p>无效</p> <!--不满足条件,不会生成这个标签-->{% elif num > 80 and num < 100 %} <p>优秀</p>{% else %} <!--也是在if标签结构里面的--> <p>凑活吧</p>{% endif %}
csrf_token标签
说明:这个标签用于跨站请求伪造保护。在页面的form表单里面(注意是在form表单里面)任何位置写上{% csrf_token %},这个东西模板渲染的时候替换成了<input type="hidden" name="csrfmiddlewaretoken" value="8J4z1wiUEXt0gJSN59dLMnktrXFW0hv7m4d40Mtl37D7vJZfrxLir9L3jSTDjtG8">,隐藏的,这个标签的值是个随机字符串{% csrf_token %}<form> <input></form>
模板继承
意义:模版继承可以让您创建一个基本的“骨架”模版,它包含您站点中的全部元素,并且可以定义能够被子模版覆盖的 blocks 。(公用的父模版)
1.父模版base.html<!DOCTYPE html><html lang="en">
<head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><title>{{ title }}</title><meta name="description" content=""><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="robots" content="all,follow"><link rel="stylesheet" href="/static/vendor/bootstrap/css/bootstrap.min.css">{% block css %}{% endblock css %} </head>
<body>
<header class="header"></header>
{% block container %}<div>子模版内容</div>{% endblock container %}
<footer class="main-footer"></footer>
<script src="/static/vendor/jquery/jquery.min.js"></script>{% block js %}{% endblock js %}</body></html>
2.一个子模版{% extends "base.html" %}{% block title %}My amazing blog{% endblock %}
{% block content %}<div>子模版内容</div>{% endblock %}
{% block js %}{% endblock %}
组件
意义:可以将常用的页面内容如导航条,页尾信息等组件保存在单独的文件中,然后在需要使用的地方,文件的任意位置按如下语法导入即可
语法:{% include 'navbar.html' %}
------------nav.html-------------<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Title</title></head><body><div class="c1"> <div> <a href="">xx</a> <a href="">dd</a> </div></div></body></html>-------------------------------嵌入导航栏的页面,test.html---------------------------<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Title</title></head><body>
{% include 'nav.html' %}
<h1>xxxxxxxxxx</h1></body></html>
自定义标签和过滤器
1、在settings中的INSTALLED_APPS配置当前app,不然django无法找到自定义的simple_tag.
并在项目应用目录app下创建templatetags文件夹,里面做标签和过滤器
-----------DjangoPro/app/templatetags/my_tags.py--------------------------------
#必须模块from django import templatefrom django.utils.safestring import mark_safe
#register的名字是固定的,不可改变register = template.Library()
#自定义标签@register.simple_tagdef simple_tag_add(v1, v2): return v1 + v2
#自定义过滤器@register.filterdef filter_multi(v1, v2): return v1 * v2
@register.simple_tagdef my_input(id, arg): result = "<input type='button' name='%s' style='%s' />" % (name, arg,) return mark_safe(result)
使用这个过滤器和标签
#在html页面导入my_tags.py,并使用自定义标签和自定义过滤器{% load tags %} {% simple_tag_add 100 200 %} {{ 50|filter_multi:40 }}{% my_input "my_input" "background-color:#FFB90F" %}
自定义标签不可以用在{% if %} {% endif %} {% for %} {% endfor %} 语句中
自定义过滤器可以
django<br />{% if 10|filter_multi:30 > 100 %}<br /> {{ 10|filter_multi:30 }}<br />{% endif %}<br />
使用inclusion_tag
说明:类似于上面的组件,只不过他需要从后端得到数据才能完成渲染成功。多用于返回html代码片段
1.写视图函数得到页面所需要的数据,将数据return回来,2.给这个视图函数加上@register.inclusion_tag(filename='left_menu.html'),以指定html文件将数据传入。在需要用到自定义inclusion_tag的html文件中引入,引入固定规则为: 通过使用{% load py文件名 %} {% 对应html文件名 py文件中的视图函数所需参数 %}的固定形式引用自定义的inclusion_tag。
![](/assets/blank.gif)
详细:https://www.jianshu.com/p/b23c77852e33
4.DjangoORM模型
1.介绍:
概述:ORM是“对象-关系-映射”的简称。(Object Relational Mapping,简称ORM)orm其实就是将类对象的语法翻译成sql语句的一个引擎.特点:数据模型的设计不需要依赖于特定的数据库,通过简单的配置就可以轻松更换数据库,这极大的减轻了开发人员的工作量,不需要面对因数据库变更而导致的无效劳动.流程:类对象--->sql--->pymysql--->mysql服务端--->磁盘.总结:一个类对于数据库一张表。
2.ORM与数据库对应关系
类---------->表类对象 ---------->行(记录)类属性 ---------->表的字段(重点)
3.结构:
#在app下的models.py文件中,一个类对于数据库一张表。class User(modelclass Book(models.Model): id = models.AutoField(primary_key=True) # 如果表里面没有写主键,表里面会自动生成一个自增主键字段,叫做id字段,orm要求每个表里面必须要写一个主键 name = models.CharField(max_length=32) # 和varchar(32)是一样的,32个字符 birthday = models.DateField() # 必须存这种格式"2018-12-12" def __str__(self): #类对象显示name值 return self.name
参数详细:https://www.cnblogs.com/clschao/articles/10427807.html
4.使用mysql数据库配置
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), }, 'app01':{#可以为每个app都配置自己的数据,并且数据库还可以指定别的,也就是不一定就是 mysql,也可以指定sqlite等其他的数据库 'ENGINE': 'django.db.backends.mysql', 'NAME': 'db1', # 要连接的数据库,连接前需要创建好 'USER': 'root',# 连接数据库的用户名 'PASSWORD': '123',# 连接数据库的密码 'HOST': '127.0.0.1',# 连接主机,默认本机 'PORT':3306 # 端口 默认3306 }}
#在项目名下的__init__.py文件中写入以下代码即可使用mysqlimport pymysqlpymysql.install_as_MySQLdb()
5.同步命令:
settings.py中 INSTALL_APP中必须配置好app
python manage.py makemigrationspython manage.py migrate
6.因为python版本无法使用mysql的解决问题
错误提示:django.core.exceptions.ImproperlyConfigured: mysqlclient 1.3.3 or newer is required; you have 0.7.11.None原因:MySQLclient目前只支持到python3.4,因此如果使用的更高版本的python,需要修改如下:办法: 通过查找路径C:\Programs\Python\Python36-32\Lib\site-packages\Django-2.0-py3.6.egg\django\db\backends\mysql 这个路径里的文件把if version < (1, 3, 3): raise ImproperlyConfigured("mysqlclient 1.3.3 or newer is required; you have %s" % Database.__version__) 注释掉。
7.增删改查(单表操作)
增加:
1.方式 jack_obj = models.User(name='jack') jack_obj.save()2.方式 models.User.objects.create(name='jack')
3.批量插入 user_list = [ models.User(name='alex'), models.User(name='mick'), models.User(name='python') ] models.User.objects.bulk_create(user_list)4.update_or_create:有就更新,没有就创建 ,还有个get_or_create,有就查询出来,没有就创建 obj,created = models.UserToken.objects.update_or_create( user=user, # 查找筛选条件 defaults={ # 添加或者更新的数据 "token":random_str, } )
查询:
models.User.objects.all() 获取所有
models.User.objects.filter(**kwargs): 它包含了与所给筛选条件相匹配的对象,结果也是queryset类型
models.User.objects.get(**kwargs): 返回与所给筛选条件相匹配的对象,不是queryset类型,是行记录对象,返回结果有且只有一个,如果符合筛选条件的对象超过一个或者没有都会抛出错误。捕获异常
models.User.objects.exclude(**kwargs): 排除.它包含了与所给筛选条件不匹配的对象,返回值是queryset类型 :User.objects.exclude(id=6),返回id不等于6的所有的对象
models.User.objects.order_by(*field): queryset类型的数据来调用,对查询结果排序,默认是按照id来升序排列的,返回值还是queryset类型
models.User.objects.reverse(): queryset类型的数据来调用,对查询结果反向排序,返回值还是queryset类型
models.User.objects.count(): queryset类型的数据来调用,返回数据库中匹配查询(QuerySet)的对象数量。
models.User.objects.first(): queryset类型的数据来调用,返回第一条记录 :User.objects.all()[0] = User.objects.all().first()
models.User.objects.last(): queryset类型的数据来调用,返回最后一条记录
models.User.objects.exists(): queryset类型的数据来调用,如果QuerySet包含数据,就返回True,否则返回False
models.User.objects.values(*field): 用的比较多,返回的是一个字典序列
models.User.objects.values_list(*field): 返回的是一个元组序列
models.User.objects.distinct(): values和values_list得到的queryset类型的数据来调用,从返回结果中剔除重复纪录'
详细:https://www.cnblogs.com/clschao/articles/10427807.html
性能优化查询:
def select_related(self, *fields) 性能相关:表之间进行join连表操作,一次性获取关联的数据。 总结: 1. select_related主要针一对一和多对一关系进行优化。 2. select_related使用SQL的JOIN语句进行优化,通过减少SQL查询的次数来进行优化、提高 性能。
def prefetch_related(self, *lookups) 性能相关:多表连表操作时速度会慢,使用其执行多次SQL查询在Python代码中实现连表操作。 总结: 1. 对于多对多字段(ManyToManyField)和一对多字段,可以使用prefetch_related()来进 行优化。 2. prefetch_related()的优化方式是分别查询每个表,然后用Python处理他们之间的关系。
def annotate(self, *args, **kwargs) # 用于实现聚合group by查询 from django.db.models import Count, Avg, Max, Min, Sum v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id')) # SELECT u_id, COUNT(ui) AS `uid` FROM UserInfo GROUP BY u_id v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id')).filter(uid__gt=1) # SELECT u_id, COUNT(ui_id) AS `uid` FROM UserInfo GROUP BY u_id having count(u_id) > 1 v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id',distinct=True)).filter(uid__gt=1) # SELECT u_id, COUNT( DISTINCT ui_id) AS `uid` FROM UserInfo GROUP BY u_id having count(u_id) > 1
执行原生SQL语句raw查询:
models.User.objects.raw('select * from appuser')
基于双下划线模糊查询:
Book.objects.filter(price__in=[100,200,300]) #price值等于这三个里面的任意一个的对象
Book.objects.filter(price__gt=100) #大于,大于等于是price__gte=100,别写price>100,这种参数不支持
Book.objects.filter(price__lt=100)
Book.objects.filter(price__range=[100,200]) #sql的between and,大于等于100,小于等于200
Book.objects.filter(title__contains="python") #title值中包含python的
Book.objects.filter(title__icontains="python") #不区分大小写
Book.objects.filter(title__startswith="py") #以什么开头,istartswith 不区分大小写
Book.objects.filter(pub_date__year=2012)
删除:
models.User.objects.all().delete() #它运行时立即删除对象而不返回任何值 models.User.objects.filter(id=1).delete()
修改:
1. models.User.objects.filter(id=1).update(name='ooo') 2. obj = models.User.objects.filter(id=1) obj.name ='ooo' obj.save()
8.增删改查(多表操作)
多表概述:
简述:一对一、多对一、多对多 ,用book表和publish表自己来想想关系,想想里面的操作,加外键约束和不加外键约束的区别,一对一的外键约束是在一对多的约束上加上唯一约束。
结构:
文字描述:---------------------------------------------------------------------------------- 作者模型:一个作者有姓名和年龄。 作者详细模型:把作者的详情放到详情表,包含生日,手机号,家庭住址等信息。作者详情模型和作者模型之间是一对一的关系(one-to-one) 出版商模型:出版商有名称,所在城市以及email。 书籍模型: 书籍有书名和出版日期,一本书可能会有多个作者,一个作者也可以写多本书,所以作者和书籍的关系就是多对多的关联关系(many-to-many);一本书只应该由一个出版商出版,所以出版商和书籍是一对多关联关系(one-to-many)。模型建立:-----------------------------------------------------------------------------------from django.db import models
class Author(models.Model): nid = models.AutoField(primary_key=True) name=models.CharField( max_length=32) age=models.IntegerField() authorDetail=models.OneToOneField(to="AuthorDetail",to_field="nid",on_delete=models.CASCADE)
class AuthorDetail(models.Model):# nid = models.AutoField(primary_key=True) birthday=models.DateField() telephone=models.BigIntegerField() addr=models.CharField( max_length=64)
class Publish(models.Model): nid = models.AutoField(primary_key=True) name=models.CharField( max_length=32) city=models.CharField( max_length=32) email=models.EmailField()
#多对多的表关系,mysql是手动创建一个第三张表,每个字段外键关联到另外两张多对多关系的表,orm的manytomany自动帮我们创建第三张表,两种方式建立关系都可以,以后的学习我们暂时用orm自动创建的第三张表,因为手动创建的第三张表我们进行orm操作的时候,很多关于多对多关系的表之间的orm语句方法无法使用#如果你想删除某张表,你只需要将这个表注销掉,然后执行那两个数据库同步指令就可以了,自动就删除了。class Book(models.Model): nid = models.AutoField(primary_key=True) title = models.CharField( max_length=32) publishDate=models.DateField() price=models.DecimalField(max_digits=5,decimal_places=2) publish=models.ForeignKey(to="Publish",to_field="nid",on_delete=models.CASCADE) # 与Author表建立多对多的关系,ManyToManyField可以建在两个模型中的任意一个,自动创建第三张表,并且注意一点,你查看book表的时候,你看不到这个字段,因为这个字段就是创建第三张表的意思,不是创建字段的意思,所以只能说这个book类里面有authors这个字段属性 authors=models.ManyToManyField(to='Author',) #注意不管是一对多还是多对多,写to这个参数的时候,最后后面的值是个字符串,不然你就需要将你要关联的那个表放到这个表的上面
结构-多对多表中自动创建第三张表
class Boy(models.Model): name = models.CharField(max_length=35)
class Girl(models.Model): name = models.CharField(max_length=33)
class BoyToGirl(models.Model): boy = models.ForeignKey(to='Boy',on_delete=models.CASCADE) girl = models.ForeignKey(to='Girl',on_delete=models.CASCADE)
结构-通过ManyToManyField自动创建第三张表
class Boy(models.Model): name = models.CharField(max_length=35) BoyLoveGirls = models.ManyToManyField(to='Girl')
class Girl(models.Model): name = models.CharField(max_length=33)
结构-多表参数
1.一对一字段to 设置要关联的表。to_field 设置要关联的字段。 on_delete 同ForeignKey字段。
2.一对多字段to 设置要关联的表to_field 设置要关联的表的字段related_name 反向操作时,使用的字段名,用于代替原反向查询时的'表名_set'。related_query_name 反向查询操作时,使用的连接前缀,用于替换表名。on_delete 当删除关联表中的数据时,当前表与其关联的行的行为。
3.多对多字段: to 设置要关联的表 related_name 同ForeignKey字段。 related_query_name 同ForeignKey字段。 through 在使用ManyToManyField字段时,Django将自动生成一张表来管理多对多的关联关系。但我们也可以手动创建第三张表来管理多对多关系,此时就需要通过 through来指定第三张表的表名。 through_fields 设置关联的字段。 db_table 默认创建第三张表时,数据库中表的名称。
4.元信息 ORM对应的类里面包含另一个Meta类,而Meta类封装了一些数据库的信息。主要字段如下:class Author2Book(models.Model): author = models.ForeignKey(to="Author") book = models.ForeignKey(to="Book") class Meta: unique_together = ("author", "book")
db_table ORM在数据库中的表名默认是 app_类名,可以通过db_table可以重写表名。db_table = 'book_model'index_together 联合索引。unique_together 联合唯一索引。ordering 指定默认按什么字段排序。 ordering = ['pub_date',] 只有设置了该属性,我们查询到的结果才可以被reverse(),否则是能对排序了的结果进行反转(order_by()方法排序过的数据)
增:
1.一对多方式1: publish_obj=Publish.objects.get(nid=1) #拿到nid为1的出版社对象 book_obj=Book.objects.create(title="金瓶眉",publishDate="2012-12-12",price=100,publish=publish_obj) #出版社对象作为值给publish,其实就是自动将publish字段变成publish_id,然后将publish_obj的id给取出来赋值给publish_id字段,注意你如果不是publish类的对象肯定会报错的,别乱昂
方式2: book_obj=Book.objects.create(title="金瓶眉",publishDate="2012-12-12",price=100,publish_id=1) #直接可以写id值,注意字段属性的写法和上面不同,这个是publish_id=xxx,上面是publish=xxx。
2.多对多方式一: 多对多一般在前端页面上使用的时候是多选下拉框的样子来给用户选择多个数据,这里可以让用户选择多个书籍,多个作者
# 当前生成的书籍对象 book_obj=Book.objects.create(title="追风筝的人",price=200,publishDate="2012-11-12",publish_id=1)
# 为书籍绑定的做作者对象 yuan=Author.objects.filter(name="yuan").first() # 在Author表中主键为2的纪录,注意取的是author的model对象 egon=Author.objects.filter(name="alex").first() # 在Author表中主键为1的纪录 #有人可能会说,我们可以直接给第三张表添加数据啊,这个自动生成的第三张表你能通过models获取到吗,是获取不到的,用不了的,当然如果你知道了这个表的名字,那么你通过原生sql语句可以进行书的添加,所以要通过orm间接的给第三张表添加数据,如果是你手动添加的第三张表你是可以直接给第三张表添加数据 # 绑定多对多关系,即向关系表book_authors中添加纪录,给书添加两个作者,下面的语法就是告诉orm给第三张表添加两条数据 book_obj.authors.add(yuan,egon) # 将某些特定的 model 对象添加到被关联对象集合中。 ======= book_obj.authors.add(*[]) #book_obj是书籍对象,authors是book表里面那个多对多的关系字段名称。 #其实orm就是先通过book_obj的authors属性找到第三张表,然后将book_obj的id值和两个作者对象的id值组合成两条记录添加到第三张表里面去方式二 book_obj.authors.add(1,2) book_obj.authors.add(*[1,2]) #这种方式用的最多,因为一般是给用户来选择,用户选择是多选的,选完给你发送过来的就是一堆的id值
删除:
book_obj = models.Book.objects.filter(nid=4)[0] # book_obj.authors.remove(2) #将第三张表中的这个book_obj对象对应的那个作者id为2的那条记录删除 # book_obj.authors.clear() # book_obj.authors.set('2') #先清除掉所有的关系数据,然后只给这个书对象绑定这个id为2的作者,所以只剩下一条记录 3---2,比如用户编辑数据的时候,选择作者发生了变化,那么需要重新选择,所以我们就可以先清空,然后再重新绑定关系数据,注意这里写的是字符串,数字类型不可以
一对一和一对多的删改和单表的删改是一样的
修改:
book_obj = models.Book.objects.get(id=1) #获取一个书籍对象data = {'title':'xxx','price':100} #这个书籍对象更新后的数据models.Book.objects.filter(id=n).update(**data) #将新数据更新到原来的记录中book_obj.authors.set(author_list) #将数据和作者的多对多关系加上
查询:
一对多查询
python<br />正向查询:<br />关联属性字段所在的表查询被关联表的记录就是正向查询<br />例子:<br />book_obj=Book.objects.filter(pk=1).first()</p> <h1 id="hbook">book对象.外键字段名称</h1> <p>book_obj.publish.city</p></li> </ol> ¨K116K
反向查询:
被关联表的记录查询关联属性字段所在的表
例子:
publish=Publish.objects.get(name="苹果出版社")
publish.book_set.all() : 与苹果出版社关联的所有书籍对象集合,
写法:小写的表名_set.all(),得到queryset类型数据img
2.一对一查询
python 正向查询: egon=Author.objects.filter(name="egon").first() egon.authorDetail.telephone :作者对象.字段名
¨K116K
反向查询(按表名:author):不需要_set,因为一对一正向反向都是找到一条记录
authorDet=AuthorDetail.objects.filter(addr="beijing")[0]
authorDet.author.nameimg
3.多对多查询
类中结构指定related_name : publish = ForeignKey(Book, related_name='bookList') # 查询 人民出版社出版过的所有书籍 publish=Publish.objects.get(name="人民出版社") publish.bookList.all() # 与人民出版社关联的所有书籍对象集合
img
#### 4.基于双下划线的跨表查询(基于join实现的)
说明:正向查询按字段,反向查询按表名小写用来告诉ORM引擎join哪张表,一对一、一对多、多对多都是一个写法,注意,我们写orm查询的时候,哪个表在前哪个表在后都没问题,因为走的是join连表操作。
python 1.一对多查询 查询苹果出版社出版过的所有书籍的名字与价格(一对多) # 正向查询 按字段:publish queryResult=Book.objects.filter(publish__name="苹果出版社") #通过__告诉orm将book表和publish表进行join,然后找到所有记录中publish.name='苹果出版社'的记录(注意publish是属性名称),然后select book.title,book.price的字段值.values_list("title","price") #values或者values_list # 反向查询 按表名:book queryResult=Publish.objects.filter(name="苹果出版社").values_list("book__title","book__price")
2.多对多查询
查询yuan出过的所有书籍的名字(多对多)
# 正向查询 按字段:authors:
queryResult=Book.objects.filter(authors__name="yuan").values_list("title")
# 反向查询 按表名:book
queryResult=Author.objects.filter(name="yuan")
.values_list("book__title","book__price")3.一对一查询
# 查询yuan的手机号
# 正向查询
Author.objects.filter(name="yuan").values("authordetail__telephone")
# 反向查询
AuthorDetail.objects.filter(author__name="yuan").values("telephone")¨K119K
.values_list("title","authors__name")
# 反向查询
queryResult=Publish.objects.filter(name="人民出版社")
.values_list("book__title","book__authors__age","book__authors__name")¨K120K
.values_list("title","publish__name")
# 方式2:
Author.objects.filter(authordetail__telephone__startswith="151")
.values("book__title","book__publish__name")4.related_name
反向查询时,如果定义了related_name ,则用related_name替换 表名,例如:
publish = ForeignKey(Blog, related_name='bookList')
查询人民出版社出版过的所有书籍的名字与价格(一对多)¨K121K
.values_list("bookList__title","bookList__price")
#### 5.聚合查询,分组查询,F查询和Q查询
###### 聚合查询aggregate(args,*kwargs)
python #它返回一个包含一些键值对的字典。键的名称是聚合值的标识符,值是计算出来的聚合值。键的名称是按照字段和聚合函数的名称自动生成出来的。如果你想要为聚合值指定一个名称,可以向聚合子句提供它 from django.db.models import Avg, Max, Min models.Book.objects.all().aggregate(Avg('price')) models.Book.objects.all().aggregate(Min("price")) models.Book.objects.all().aggregate(Max("price"))
###### 分组查询
python -----单表查询------ #annotate里面必须写个聚合函数,不然没有意义,并且必须有个别名=,别名随便写,但是必须有,用哪个字段分组,values里面就写哪个字段,annotate其实就是对分组结果的统计 SQL:select price,Count(*) from book group by price; ORM:Book.objects.values("price").annotate(c=Count("nid")) -----多表查询------- 跨表分组查询本质就是将关联表join成一张表,再按单表的思路进行分组查询,,既然是join连表,就可以使用咱们的双下划线进行连表 -----------------案例------------------- Publish.objects.annotate(MinPrice=Min("book__price"))
###### F查询
python #用F()查询将表中的连个字段作比较,F可以取字段 # 查询评论数大于收藏数的书籍 from django.db.models import F Book.objects.filter(commentNum__lt=F('keepNum')) # 查询评论数大于收藏数2倍的书籍 Book.objects.filter(commentNum__lt=F('keepNum')*2) #修改操作也可以使用F函数,比如将每一本书的价格提高30元: Book.objects.all().update(price=F("price")+30)
###### Q查询
python #Q 对象可以使用&(与) 、|(或)、~(非) 操作符组合起来。当一个操作符在两个Q 对象上使用时,它产生一个新的Q 对象。
SQL:Author.objects.filter(Q(name='ales')|Q(name='egon')).first().name
ORM:select name from app01_author where name='ales' or name='egon'##### 补充:直接执行自定义SQL
python 有时候raw()方法并不十分好用,很多情况下我们不需要将查询结果映射成模型,或者我们需要执行DELETE、 INSERT以及UPDATE操作。在这些情况下,我们可以直接访问数据库,完全避开模型层。 我们可以直接从django提供的接口中获取数据库连接,然后像使用pymysql模块一样操作数据库。
from django.db import connection, connections
cursor = connections['default'].cursor()
cursor = connection.cursor()
cursor.execute("""SELECT * from auth_user where id = %s""", [1])
ret = cursor.fetchone()## 5.DjangoORM优化
### QuerySet
python 1.可切片:Book.objects.all()[0:2] 2.可迭代:for i in Book.objects.all():print(i.title) 3.缓存机制 : 让查询结果缓存起来,book_obj = Book.objects.all(),减少访问数据库的负载 4.exists()方法: if book_obj.exists():print("keep go"); 5.iterator()方法: 处理成千上万的记录时,将它们一次装入内存是很浪费的。避免在遍历数据的同时产生queryset cache。使用iterator()用来获取数据,处理完数据就将其丢弃。 book_obj = Book.objects.all().iterator() 6.总结: queryset的cache是用于减少程序对数据库的查询,在通常的使用下会保证只有在需要的时候才会查询数据库。 使用exists()和iterator()方法可以优化程序对内存的使用。不过,由于它们并不会生成queryset cache,可能 会造成额外的数据库查询。
### 使用select_related查询优化‘
python 1.意义:对于一对一字段(OneToOneField)和外键字段(ForeignKey),可以使用select_related 来对QuerySet进行优化。
2.作用:在对QuerySet使用select_related()函数后,Django会获取相应外键对应的对象,从而在之后需要的时候不必再查询数据库了
--------------------------------例子--------------------------------------
Book.objects.select_related("publish").first().name
优于
Book.objects.filter(nid=1).publish.name
查询速度3.总结:
select_related主要针一对一和多对一关系进行优化。
select_related使用SQL的JOIN语句进行优化,通过减少SQL查询的次数来进行优化、提高性能。
可以通过可变长参数指定需要select_related的字段名。也可以通过使用双下划线“__”连接字段名来实现指定的递归查询。
没有指定的字段不会缓存,没有指定的深度不会缓存,如果要访问的话Django会再次进行SQL查询。
也可以通过depth参数指定递归的深度,Django会自动缓存指定深度内所有的字段。如果要访问指定深度外的字段,Django会再次进行SQL查询。
也接受无参数的调用,Django会尽可能深的递归查询所有的字段。但注意有Django递归的限制和性能的浪费。
Django >= 1.7,链式调用的select_related相当于使用可变长参数。Django < 1.7,链式调用会导致前边的select_related失效,只保留最后一个。### 使用prefetch_related()查询优化
python 1.意义:对于多对多字段(ManyToManyField)和一对多字段,可以使用prefetch_related()来进行优化
2.作用:分别查询每个表,然后用Python处理他们之间的关系。
----------------案例---------------
article_obj=models.Article.objects.prefetch_related("tags").all()
优于
article_obj=models.Article.objects.all()
查询速度#### prefetch_related()和select_related()区别:
prefetch_related()和select_related()的设计目的很相似,都是为了减少SQL查询的数量,但是实现的方式不一样。后者是通过JOIN语句,在SQL查询内解决问题。但是对于多对多关系,使用SQL语句解决就显得有些不太明智,因为JOIN得到的表将会很长,会导致SQL语句运行时间的增加和内存占用的增加。若有n个对象,每个对象的多对多字段对应Mi条,就会生成Σ(n)Mi 行的结果表。
#### 使用extra方法实现复杂SQL语句
意义:Django的查询语法难以简单的表达复杂的
WHERE
子句,对于这种情况, Django 提供了extra()
QuerySet
修改机制 — 它能在QuerySet
生成的SQL从句中注入新子句Author.objects.extra(select={'name':"1"})
Author.objects.extra(where=['name="alex"'])
#### 使用bulk_create()整体插入减少插入语句的数量。
python user_list = [ models.User(name='alex'), models.User(name='mick'), models.User(name='python') ] models.User.objects.bulk_create(user_list)
### ORM事务控制
python 1.全局事务(!!!不推荐): 统一个http请求对应的所有sql都放在一个事务中执行(要么所有都成功,要么所有都失败)。是全局性的配置, 如果要对某个http请求放水(然后自定义事务),可以用non_atomic_requests修饰器
2.settings配置:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'mxshop',
'HOST': '127.0.0.1',
'PORT': '3306',
'USER': 'root',
'PASSWORD': '123',
'OPTIONS': {
"init_command": "SET default_storage_engine='INNODB'",
#'init_command': "SET sql_mode='STRICT_TRANS_TABLES'", #配置开启严格sql模式¨K122K
'other':{
'ENGINE': 'django.db.backends.mysql',
......
} #还可以配置其他数据库
}python 1.局部使用事务(推荐): 详细:https://www.cnblogs.com/clschao/articles/10463267.html
1.Django之Ajax
Ajax (Asynchronous Javascript And XML)
作用:Javascript语言与服务器进行异步交互,传输的数据为XML(当然,传输的数据不只是XML,现在更多使用json数据)在不加载整个页面的情况下,与服务器端数据交互。
优点:使用javascript向服务器发送异步请求;无须刷新整个页面;性能高;
特点:浏览器页面局部刷新。
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script>$("#b1").on("click", function () {$.ajax({url:"/login",type:"POST", data:{"i1":$("#i1").val(),"i2":$("#i2").val()}, //object类型,键值形式的,可以不给键加引号success:function (data) {$("#i3").val(data);}})})
应用场景:搜索引擎根据用户输入的关键字,自动提示检索关键字;网站动态注册登录;
Ajax参数
data:
当前ajax请求要携带的数据,是一个json的object对象,ajax方法就会默认地把它编码成某种格式
(urlencoded:?a=1&b=2)发送给服务端;此外,ajax默认以get方式发送请求。processData:
processData 默认为true,当设置为true的时候,jquery ajax 提交的时候不会序列化 data,而是直接使用datacontentType:默认值: "application/x-www-form-urlencoded"。发送信息至服务器时内容编码类型。traditional:一般是我们的data数据有数组时会用到 :data:{a:22,b:33,c:["x","y"]},traditional为false会对数据进行深层次迭代;
Ajax设置csrf_token的方法:
方式1.
通过获取隐藏的input标签中的csrfmiddlewaretoken值,放置在data中发送。$.ajax({url: "/cookie_ajax/",type: "POST",data: {"username": "chao","password": 123456,"csrfmiddlewaretoken": $("[name = 'csrfmiddlewaretoken']").val() // 使用jQuery取出csrfmiddlewaretoken的值,拼接到data中},success: function (data) {console.log(data);}
})方式二.
通过获取返回的cookie中的字符串 放置在请求头中发送。
注意:需要引入一个jquery.cookie.js插件。
<script src="{% static 'js/jquery.cookie.js' %}"></script>
$.ajax({headers:{"X-CSRFToken":$.cookie('csrftoken')}, #其实在ajax里面还有一个参数是headers,自定制请求头,可以将csrf_token加在这里,我们发contenttype类型数据的时候,csrf_token就可以这样加
Content-Type:
1.application/x-www-form-urlencoded
这应该是最常见的 POST 提交数据的方式了。浏览器的原生 <form> 表单,如果不设置 enctype 属性,那么最终就会以 默认格式application/x-www-form-urlencoded 方式提交数据,ajax默认也是这个.2.multipart/form-data
这又是一个常见的 POST 数据提交的方式。我们使用表单上传文件时,必须让 <form> 表单的 enctype 等于 multipart/form-data,form表单不支持发json类型的contenttype格式的数据,而ajax什么格式都可以发,也是ajax应用广泛的一个原因。3.application/json
指定浏览器向服务端发送的数据格式,contentType:'json'。服务端接受到数据之后,通过contenttype类型的值来使用不同的方法解析数据。
基于ajax的文件上传
html:
<form>
{% csrf_token %}
用户名 <input type="text" id="user">
头像 <input type="file" id="avatar">
<input type="button" id="ajax-submit" value="ajax-submit">
</form>
<script>$("#ajax-submit").click(function(){var formdata=new FormData(); #ajax上传文件的时候,需要这个类型,它会将添加给它的键值对加工成formdata的类型formdata.append("user",$("#user").val()); #添加键值的方法是append,注意写法,键和值之间是逗号formData.append("csrfmiddlewaretoken", $("[name='csrfmiddlewaretoken']").val()); #别忘了csrf_tokenformdata.append("avatar_img",$("#avatar")[0].files[0]);$.ajax({url:"",type:"post",data:formdata, #将添加好数据的formdata放到data这里processData: false , // 不处理数据contentType: false, // 不设置内容类型success:function(data){console.log(data)}})})
</script>
views.py
def file(request):print(request.FILES)return
JSON概述
![](/assets/blank.gif)
JSON.parse(): 用于将一个 JSON 字符串转换为 JavaScript 对象
JSON.stringify(): 用于将 JavaScript 值转换为 JSON 字符串。
2.Djano中的cookie和session
cookie
意义:因为http协议是无状态的,应声出来cookie。就是服务武器发送出来存储在浏览器端的键值对。下次访问服务器浏览器会自动携带这些键值对。一边服务器提取有用信息。
原理:浏览器访问服务端能通过Cookie的内容来判断这个是“谁”了。
django操作cookie
1.获取cookierequest.COOKIES['key']request.get_signed_cookie(key, default=RAISE_ERROR, salt='', max_age=None)
2.设置cookieret = render(request,'login.html')ret.set_cookie(key,value)ret.set_signed_cookie(key,value,salt='加密盐', max_age=None, ...
3.删除cookieret = redirect("/login/")ret.delete_cookie("user")return ret
Jquery操作cookie
定义:让网站服务器把少量数据储存到客户端的硬盘或内存,从客户端的硬盘读取数据的一种技术
;
<script src="https://cdn.bootcdn.net/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.min.js"></script>
<script>/*1.添加一个‘会话cookie’。这里没有指明 cookie有效时间,所创建的cookie有效期默认到用户关闭浏览器为止,所以被称为 “会话cookie(session cookie)”。*/$.cookie('the_cookie','the_value');/*2.创建一个cookie并设置有效时间为 7天*/$.cookie('the_cookie', 'the_value', { expires: 7 });/*3.创建一个cookie并设置 cookie的有效路径,cookie的路径用于设置能够读取 cookie的顶级目录。将这个路径设置为网站的根目录,可以让所有网页都能互相读取 cookie*/$.cookie('the_cookie', 'the_value', { expires: 7, path: '/' });/*4.读取cookie*/$.cookie('the_cookie');/*5.删除cookie*/$.cookie('the_cookie',null);/*6.cookie可选参数*/$.cookie('the_cookie','the_value',{expires:7, {#(Number|Date)有效期;设置一个整数时,单位是天;也可以设置一个日期对象作为Cookie的过期日期;#}path:'/', {# (String)创建该Cookie的页面路径; #}domain:'jquery.com',{# String)创建该Cookie的页面域名; #}secure:true{# (Booblean)如果设为true,那么此Cookie的传输会要求一个安全协议,例如:HTTPS; #}})
</script>
session
Cookie虽然在一定程度上解决了“保持状态”的需求,但是由于Cookie本身最大支持4096字节,以及Cookie本身保存在客户端,可能被拦截或窃取,因此就需要有一种新的东西,它能支持更多的字节,并且他保存在服务器,有较高的安全性。这就是Session。
Cookie弥补了HTTP无状态的不足,让服务器知道来的人是“谁”;但是Cookie以文本的形式保存在本地,自身安全性较差;所以我们就通过Cookie识别不同的用户,对应的在Session里保存私密的信息以及超过4096字节的文本。
django操作session
Django的session键值对放在django-session表中
1.获取sessionrequest.session['k1']request.session.get('k1',None)
2.设置sessionrequest.session['k1'] = 123request.session.setdefault('k1',123)
3.删除sessiondel request.session['k1']4.所有 键、值、键值对
request.session.keys()
request.session.values()
request.session.items()5.会话session的key
session_key = request.session.session_key 获取sessionid的值6.将所有Session失效日期小于当前日期的数据删除,将过期的删除
request.session.clear_expired()7.检查会话session的key在数据库中是否存在
request.session.exists("session_key") #session_key就是那个sessionid的值8.删除当前会话的所有Session数据
request.session.delete()9.删除当前的会话数据并删除会话的Cookie。
request.session.flush() #常用,清空所有cookie---删除session表里的这个会话的记录,这用于确保前面的会话数据不可以再次被用户的浏览器访问例如,django.contrib.auth.logout() 函数中就会调用它。10.设置会话Session和Cookie的超时时间
request.session.set_expiry(value)* 如果value是个整数,session会在些秒数后失效。* 如果value是个datatime或timedelta,session就会在这个时间后失效。* 如果value是0,用户关闭浏览器session就会失效。* 如果value是None,session会依赖全局session失效策略。
django-session流程
![](/assets/blank.gif)
django-session配置
1. 数据库Session
SESSION_ENGINE = 'django.contrib.sessions.backends.db' # 引擎(默认)2. 缓存Session
SESSION_ENGINE = 'django.contrib.sessions.backends.cache' # 引擎
SESSION_CACHE_ALIAS = 'default' # 使用的缓存别名(默认内存缓存,也可以是memcache),此处别名依赖缓存的设置3. 文件Session
SESSION_ENGINE = 'django.contrib.sessions.backends.file' # 引擎
SESSION_FILE_PATH = None # 缓存文件路径,如果为None,则使用tempfile模块获取一个临时地址tempfile.gettempdir() 4. 缓存+数据库
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db' # 引擎5. 加密Cookie Session
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies' # 引擎其他公用设置项:
SESSION_COOKIE_NAME = "sessionid" # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串(默认)SESSION_COOKIE_PATH = "/" # Session的cookie保存的路径(默认)SESSION_COOKIE_DOMAIN = None # Session的cookie保存的域名(默认)SESSION_COOKIE_SECURE = False # 是否Https传输cookie(默认)SESSION_COOKIE_HTTPONLY = True # 是否Session的cookie只支持http传输(默认)SESSION_COOKIE_AGE = 1209600 # Session的cookie失效日期(2周)(默认)SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 是否关闭浏览器使得Session过期(默认)SESSION_SAVE_EVERY_REQUEST = False # 是否每次请求都保存Session,默认修改之后才保存(默认)
3.Django中间件
说明:介于request与response之间的的一道处理过程。在django全局上改变输入与输出。在view视图函数执行之前和执行之后的额外操作,
流程:多个中间件中的process_response方法是按照MIDDLEWARE中的注册顺序倒序执行的,也就是说第一个中间件的process_request方法首先执行,而它的process_response方法最后执行,最后一个中间件的process_request方法最后一个执行,它的process_response方法是最先执行。
应用场景:IP访问频率限制;URL访问过滤;登录校验;
![](/assets/blank.gif)
![](/assets/blank.gif)
自定义中间件
#自创建py文件。继承MiddlewareMixin类
--------------------------------md1.py-----------------------------------
from django.utils.deprecation import MiddlewareMixinclass Mymiddleware(MiddlewareMixin):def process_request(self, request):print('request方法说明请求来了要处理')passdef process_response(self, request, response):print('response方法说明响应出去时需要处理')return response
------------------------------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','utils.md1.Mymiddleware'
]
process_request参数
它的返回值可以是None也可以是HttpResponse对象。返回值是None的话,按正常流程继续走,交给下一个中间件处理,如果是HttpResponse对象,Django将不执行视图函数,而将相应对象返回给浏览器。
process_response参数
必须return response,不然上层的中间件就没有拿到Httpresponse对象,就会报错
process_view
它应该返回None或一个HttpResponse对象。 如果返回None,Django将继续处理这个请求,执行任何其他中间件的process_view方法,然后在执行相应的视图。 如果它返回一个HttpResponse对象,Django不会调用对应的视图函数。 它将执行中间件的process_response方法并将应用到该HttpResponse并返回结果。
process_exception
这个方法只有在视图函数中出现异常了才执行,它返回的值可以是一个None也可以是一个HttpResponse对象。如果是HttpResponse对象,Django将调用模板和中间件中的process_response方法,并返回给浏览器,否则将默认处理异常。如果返回一个None,则交给下一个中间件的process_exception方法来处理异常。它的执行顺序也是按照中间件注册顺序的倒序执行。
process_template_response
process_template_response是在视图函数执行完成后立即执行,但是它有一个前提条件,那就是视图函数返回的对象有一个render()方法(或者表明该对象是一个TemplateResponse对象或等价方法)。
中间件请求流程
说明:
请求到达中间件之后,先按照正序执行每个注册中间件的process_reques方法,process_request方法返回的值是None,就依次执行,如果返回的值是HttpResponse对象,不再执行后面的process_request方法,而是执行当前对应中间件的process_response方法,将HttpResponse对象返回给浏览器。也就是说:如果MIDDLEWARE中注册了6个中间件,执行过程中,第3个中间件返回了一个HttpResponse对象,那么第4,5,6中间件的process_request和process_response方法都不执行,顺序执行3,2,1中间件的process_response方法。
![](/assets/blank.gif)
![](/assets/blank.gif)
4.Django中的Form和ModelForm组件
Form组件
1.前端页面是form类的对象生成的 -->生成HTML标签功能
2.当用户名和密码输入为空或输错之后 页面都会提示 -->用户提交校验功能
3.当用户输错之后 再次输入 上次的内容还保留在input框 -->保留上次输入内容
注册实例
----------自创建form.py--------
from django import formsclass MyForm(forms.Form):username = forms.CharField(label='用户名',max_length=10,min_length=2,error_messages={'min_length':'太短了'})password = forms.CharField(label='密码',max_length=20)
----------register.html-------------
<form action="" method="post" novalidate autocomplete="off">{% csrf_token %}
1.<div><label for="{{ form_obj.username.id_for_label }}">{{ form_obj.username.label }}{{ form_obj.username }}{{ form_obj.errors.0 }}</label></div><div><label for="{{ form_obj.password.id_for_label }}">{{ form_obj.password.label }}{{ form_obj.password }}{{ form_obj.errors.0 }}</label></div>2.
{# <div>#}
{# {{ form_obj.as_p }}{{ form_obj.errors.0 }}#}
{# </div>#}<div><input type="submit" value="注册" ></div>
</form>
---------------views.py------------------
from .form import MyForm
def register(request):#保存上次输入的结果form_obj = MyForm()if request.method =='POST':form_obj = MyForm(data=request.POST)#数据校验成功跳转到indexif form_obj.is_valid():return redirect('/index/')return render(request,'register.html',{'form_obj':form_obj})
Form组件常用字段与插件
1.initial:初始值,input框里的初始值2.errror_messages : 自定义错误信息3.passowrd :passowrd = forms.CharField(min_length=6,label="密码",widget=forms.widgets.PasswordInput(attrs={'class': 'c1'}, render_value=True) )#这个密码字段和其他字段不一样,默认在前端输入数据错误的时候,点击提交之后,默认是不保存的原来数据的,但是可以通过这个render_value=True让这个字段在前端保留用户输入的数据4.radioSelect:单radio值为字符串gender = forms.ChoiceField(choices=((1, "男"), (2, "女"), (3, "保密")),label="性别",widget=forms.widgets.RadioSelect())5.单选Select:hobby = forms.ChoiceField(choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ),label="爱好",initial=3,widget=forms.widgets.Select())6.多选Select:hobby = forms.MultipleChoiceField(choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ),label="爱好",initial=[1, 3],widget=forms.widgets.SelectMultiple())7.单选checkbox:keep = forms.ChoiceField(label="是否记住密码",initial="checked",widget=forms.widgets.CheckboxInput())8.多选checkbox:hobby = forms.fields.MultipleChoiceField(choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),label="爱好",initial=[1, 3],widget=forms.widgets.CheckboxSelectMultiple())
9.date类型
from django import forms
from django.forms import widgets
class BookForm(forms.Form):date = forms.DateField(widget=widgets.TextInput(attrs={'type':'date'})) #必须指定type,不然不能渲染成选择时间的input框
form组件类参数:
Fieldrequired=True, 是否允许为空widget=None, HTML插件label=None, 用于生成Label标签或显示内容initial=None, 初始值help_text='', 帮助信息(在标签旁边显示)error_messages=None, 错误信息 {'required': '不能为空', 'invalid': '格式错误'}validators=[], 自定义验证规则localize=False, 是否支持本地化disabled=False, 是否可以编辑label_suffix=None Label内容后缀CharField(Field)max_length=None, 最大长度min_length=None, 最小长度strip=True 是否移除用户输入空白IntegerField(Field)max_value=None, 最大值min_value=None, 最小值FloatField(IntegerField)...DecimalField(IntegerField)max_value=None, 最大值min_value=None, 最小值max_digits=None, 总长度decimal_places=None, 小数位长度BaseTemporalField(Field)input_formats=None 时间格式化 DateField(BaseTemporalField) 格式:2015-09-01
TimeField(BaseTemporalField) 格式:11:12
DateTimeField(BaseTemporalField)格式:2015-09-01 11:12DurationField(Field) 时间间隔:%d %H:%M:%S.%f...RegexField(CharField)regex, 自定制正则表达式max_length=None, 最大长度min_length=None, 最小长度error_message=None, 忽略,错误信息使用 error_messages={'invalid': '...'}EmailField(CharField) ...FileField(Field)allow_empty_file=False 是否允许空文件ImageField(FileField) ...注:需要PIL模块,pip3 install Pillow以上两个字典使用时,需要注意两点:- form表单中 enctype="multipart/form-data"- view函数中 obj = MyForm(request.POST, request.FILES)URLField(Field)...BooleanField(Field) ...NullBooleanField(BooleanField)...ChoiceField(Field)...choices=(), 选项,如:choices = ((0,'上海'),(1,'北京'),)required=True, 是否必填widget=None, 插件,默认select插件label=None, Label内容initial=None, 初始值help_text='', 帮助提示ModelChoiceField(ChoiceField)... django.forms.models.ModelChoiceFieldqueryset, # 查询数据库中的数据empty_label="---------", # 默认空显示内容to_field_name=None, # HTML中value的值对应的字段limit_choices_to=None # ModelForm中对queryset二次筛选ModelMultipleChoiceField(ModelChoiceField)... django.forms.models.ModelMultipleChoiceFieldTypedChoiceField(ChoiceField)coerce = lambda val: val 对选中的值进行一次转换empty_value= '' 空值的默认值MultipleChoiceField(ChoiceField)...TypedMultipleChoiceField(MultipleChoiceField)coerce = lambda val: val 对选中的每一个值进行一次转换empty_value= '' 空值的默认值ComboField(Field)fields=() 使用多个验证,如下:即验证最大长度20,又验证邮箱格式fields.ComboField(fields=[fields.CharField(max_length=20), fields.EmailField(),])MultiValueField(Field)PS: 抽象类,子类中可以实现聚合多个字典去匹配一个值,要配合MultiWidget使用SplitDateTimeField(MultiValueField)input_date_formats=None, 格式列表:['%Y--%m--%d', '%m%d/%Y', '%m/%d/%y']input_time_formats=None 格式列表:['%H:%M:%S', '%H:%M:%S.%f', '%H:%M']FilePathField(ChoiceField) 文件选项,目录下文件显示在页面中path, 文件夹路径match=None, 正则匹配recursive=False, 递归下面的文件夹allow_files=True, 允许文件allow_folders=False, 允许文件夹required=True,widget=None,label=None,initial=None,help_text=''GenericIPAddressFieldprotocol='both', both,ipv4,ipv6支持的IP格式unpack_ipv4=False 解析ipv4地址,如果是::ffff:192.0.2.1时候,可解析为192.0.2.1, PS:protocol必须为both才能启用SlugField(CharField) 数字,字母,下划线,减号(连字符)...UUIDField(CharField) uuid类型
自定义form字段:
class MyForm(Form):user = fields.CharField(validators=[RegexValidator(r'^[0-9]+$', '请输入数字'), RegexValidator(r'^159[0-9]+$', '数字必须以159开头')],)
自定义校验规则:
import re
from django.forms import Form
from django.forms import widgets
from django.forms import fields
from django.core.exceptions import ValidationError# 自定义验证规则
def mobile_validate(value):mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$')if not mobile_re.match(value):raise ValidationError('手机号码格式错误') #自定义验证规则的时候,如果不符合你的规则,需要自己发起错误class PublishForm(Form):title = fields.CharField(max_length=20,min_length=5,error_messages={'required': '标题不能为空','min_length': '标题最少为5个字符','max_length': '标题最多为20个字符'},widget=widgets.TextInput(attrs={'class': "form-control",'placeholder': '标题5-20个字符'}))# 使用自定义验证规则phone = fields.CharField(validators=[mobile_validate, ],error_messages={'required': '手机不能为空'},widget=widgets.TextInput(attrs={'class': "form-control",'placeholder': u'手机号码'}))email = fields.EmailField(required=False,error_messages={'required': u'邮箱不能为空','invalid': u'邮箱格式错误'},widget=widgets.TextInput(attrs={'class': "form-control", 'placeholder': u'邮箱'}))
钩子方法(对字段校验)
局部钩子
方法:在Fom类中定义 clean_字段名() 方法,就能够实现对特定字段进行校验。
username = forms.CharField(label='用户名',min_length=2,error_messages={'min_length':'必须大于2个字节'},)def clean_username(self):value = self.cleaned_data['username']if 'root' in value:raise Exception('最好不要用root当用户名')else:return value
全局钩子:
方法:我们在Fom类中定义 clean() 方法,就能够实现对字段进行全局校验,字段全部验证完,局部钩子也全部执行完之后,执行这个全局钩子校验。
password = forms.CharField(label='密码',max_length=20)re_password = forms.CharField(label='确认密码',max_length=20)def clean(self):pwd = self.cleaned_data['password']re_pwd = self.cleaned_data['re_password']if pwd != re_pwd:#在re_password这个字段的错误列表中加上一个错误,并且clean_data里面会自动清除这个 re_password的值,所以打印clean_data的时候会看不到它self.add_error('re_password','两次密码不一致')raise ValueError("两次密码不一致")else:return self.cleaned_data
全局应用bootstrap样式和其他:
username = forms.CharField(min_length=8,label="用户名",initial="张三",error_messages={"required": "不能为空","invalid": "格式错误","min_length": "用户名最短8位"}...def __init__(self, *args, **kwargs):super(LoginForm, self).__init__(*args, **kwargs)for field in iter(self.fields):self.fields[field].widget.attrs.update({'class': 'form-control'})field.error_messages = {'required':"不能为空"} #全部为空的错误信息
ModelForm使用
意义:Django 提供一个辅助类让我们以Django的models.py数据来创建Form。
作用:form与model的终极结合,会根据你model中的字段转换成对应的form字段,并且并你生成标签等操作。
例子:
-----------models.py---------------------
class Book(models.Model):nid = models.AutoField(primary_key=True)title = models.CharField( max_length=32)publishDate=models.DateField()price=models.DecimalField(max_digits=5,decimal_places=2)publish=models.ForeignKey(to="Publish",to_field="nid")authors=models.ManyToManyField(to='Author',)def __str__(self):return self.title
-----------forms.py-------------------
from .models import Book
class BookForm(forms.ModelForm):class Meta:model = Bookfields = '__all__'labels = {'title':'书名','price':'价格'}
ModelForm中class Meta参数:
model = Book # 对应的Model中的类
fields = "__all__" # 字段,如果是__all__,就是表示列出所有的字段
exclude = None # 排除的字段
labels = None # 提示信息
help_texts = None # 帮助提示信息
widgets = None # 自定义插件
error_messages = None # 自定义错误信息
error_messages = {'title':{'required':'不能为空'}
}
ModelForm的验证:
1.与普通的Form表单验证类型类似,ModelForm表单的验证在调用is_valid() 或访问errors 属性时隐式调用。
2.我们可以像使用Form类一样自定义局部钩子方法和全局钩子方法来实现自定义的校验规则。
3.如果我们不重写具体字段并设置validators属性的话,ModelForm是按照模型中字段的validators来校验的。
save()方法
>>> from myapp.models import Book
>>> from myapp.forms import BookForm# 根据POST数据创建一个新的form对象
>>> form_obj = BookForm(request.POST)# 创建书籍对象
>>> new_ book = form_obj.save()# 基于一个书籍对象创建form对象
>>> edit_obj = Book.objects.get(id=1)
# 使用POST提交的数据更新书籍对象
>>> form_obj = BookForm(request.POST, instance=edit_obj)
>>> form_obj.save()
Form与ModelForm总结:
ModelsForm:功能就是把model和form组合起来。通过models字段进行判断。1.创建数据:调用save()方法即可2.编辑数据:instance=new_obj;obj是要修改的数据库的一条数据的对象
Form:自己写判断内容。
ModelForm简单例子
----------------------------------------forms.py-------------------------------
from django.forms import ModelFormclass StudentList(ModelForm):class Meta:model =Student #对应的Model中的类fields = "__all__" #字段,如果是__all__,就是表示列出所有的字段exclude = None #排除的字段#error_messages用法:error_messages = {'name':{'required':"用户名不能为空",},'age':{'required':"年龄不能为空",},}#widgets用法,比如把输入用户名的input框给为Textarea#首先得导入模块from django.forms import widgets as wid #因为重名,所以起个别名widgets = {"name":wid.Textarea(attrs={"class":"c1"}) #还可以自定义属性}#labels,自定义在前端显示的名字labels= {"name":"用户名"}----------------------------------------views.py-------------------------------
def student(request):if request.method == 'GET':student_list = StudentList()return render(request,'student.html',{'student_list':student_list})
-------------------------------html------------------------------------------------
1.前端只需要 {{ student_list.as_p }} 一下,所有的字段就都出来了。
2.不过使用 for循环这个student_list,拿到student对象,直接在前端打印这个student,是个input框student.label ,拿到数据库中每个字段的verbose_name ,如果没有设置这个属性,拿到的默认就是字段名,还可以通过student.errors.0 拿到错误信息有了这些,我们就可以通过bootstrap,自己拼出来想要的样式了
<div class="container"><h1>student</h1><form method="POST" novalidate>{% csrf_token %}1.{# {{ student_list.as_p }}#}2.{% for student in student_list %}<div class="form-group col-md-6">{# 拿到数据字段的verbose_name,没有就默认显示字段名 #}<label class="col-md-3 control-label">{{ student.label }}</label><div class="col-md-9" style="position: relative;">{{ student }}</div></div>{% endfor %}<div class="col-md-2 col-md-offset-10"><input type="submit" value="提交" class="btn-primary"></div></form>
</div>
</body>
5.Django内置分页器
https://www.cnblogs.com/clschao/articles/10478846.html
6.DjangoAuth认证系统
Auth模块
默认使用auth_user表来存储数据,使用auth模块来进行用户认证。
Django中默认生成的auth_user表中的字段
username: 用户名。150个字符以内。可以包含数字和英文字符,以及_、@、+、.和-字符。不能为空,且必须唯一!
first_name:歪果仁的first_name,在30个字符以内。可以为空。
last_name:歪果仁的last_name,在150个字符以内。可以为空。
email:邮箱。可以为空。
password:密码。经过哈希过后的密码。
#groups:分组。一个用户可以属于多个分组,一个分组可以拥有多个用户。groups这个字段是跟Group的一个多对多的关系。
#user_permissions:权限。一个用户可以拥有多个权限,一个权限可以被多个用户所有用。和Permission属于一种多对多的关系。
is_staff:是否可以进入到admin的站点。代表是否是员工。这个字段如果不使用admin的话,可以自行忽略,不影响使用
is_active:是否是可用的。对于一些想要删除账号的数据,我们设置这个值为False就可以了,而不是真正的从数据库中删除。
is_superuser:是否是超级管理员。如果是超级管理员,那么拥有整个网站的所有权限。
last_login:上次登录的时间。
date_joined:账号创建的时间。
Auth模块中方法:
1.authenticate()
验证用户名以及密码是否正确,如果认证成功(用户名和密码正确有效,就是去auth_user表中查询一下是否存在这条记录),便会返回一个 User 对象,查询认证失败返回None。authenticate()会在该 User 对象上设置一个属性来标识后端已经认证了该用户,且该信息在后续的登录过程中是需要的。
用法:user = auth.authenticate(username='theuser',password='thepassword')2.login(HttpRequest, user)
该函数接受一个HttpRequest对象,以及一个经过认证的User对象。该函数实现一个用户登录的功能。它本质上会在后端为该用户生成相关session数据,
比如:request.session['user_id'] = 'user_id'保持会话用。只要使用login(request, user_obj)之后,request.user就能拿到当前登录的用户对象。否则request.user得到的是一个匿名用户对象(AnonymousUser Object,是request.user的默认值)3.logout(request)
当调用该函数时,当前请求的session信息会全部清除。该用户即使没有登录,使用该函数也不会报错。
相当于执行了request.session.flush()。-------------------------------------------------代码------------------------
def logins(request):if request.method == 'POST':username = request.POST.get('username')password = request.POST.get('password')user_obj = auth.authenticate(username=username,password=password)if user_obj:# 可以简单理解为request.session['user_id']=user_id,# 并且将user_obj封装到了request里面,# 通过request.user=user_objauth.login(request,user=username)return redirect('/index/')return render(request,'login.html')return render(request,'login.html')
User对象
如果是真正的 User 对象,返回值恒为 True 。 用于检查用户是否已经通过了认证。通过认证并不意味着用户拥有任何权限,甚至也不检查该用户是否处于激活状态,这只是表明用户成功的通过了认证。 这个方法很重要, 在后台用request.user.is_authenticated()判断用户是否已经登录,如果true则可以向前台展示request.user.name。
1。用户登陆后才能访问某些页面,
2。如果用户没有登录就访问该页面的话直接跳到登录页面
3。用户在跳转的登陆界面中完成登陆后,自动访问跳转到之前访问的地址
login_requierd():登录校验
1.若用户没有登录,则会跳转到django默认的 登录URL '/login/ ' (这个值可以在settings文件中通过LOGIN_URL进行修改)。并传递 当前访问url的绝对路径 (登陆成功后,会重定向到该路径)。from django.contrib.auth.decorators import login_required
@login_required
def book(request):return HttpResponse('this is book')2.settings.py文件中有一个认证装饰器@login_required需要用的一个配置项:
LOGIN_URL = '/login/' #配置装饰器跳转的登陆的url
create_user()
auth 提供的一个创建新用户的方法,需要提供必要参数(username、password)等
from django.contrib.auth.models import User
user = User.objects.create_user(username='用户名',password='密码',email='邮箱',...)
create_superuser()
auth 提供的一个创建新的超级用户的方法,需要提供必要参数(username、password)等。
from django.contrib.auth.models import User
user_obj = User.objects.create_superuser(username='用户名',password='密码',email='邮箱',...)
set_password()
auth 提供的一个修改密码的方法,接收要设置的新密码 作为参数。设置完一定要调用用户对象的save方法
user_obj.set_password('新密码') #user_obj其实就是request.user
user_obj.save()
user_obj能够拿到认证所用用户表的数据属性,比如username, password等。
其他常用属性含义如下:
is_staff : 用户是否拥有网站的管理权限.
is_active : 是否允许用户登录, 设置为 False,可以在不删除用户的前提下禁止用户登录。
扩展默认的auth_user表
说明:
通过继承内置的 AbstractUser 类,来定义一个自己的Model类。django给我们自动创建的一张user表,而如果要用auth模块,就必须要使用(或继承)这张表。继承表的好处是我们可以增加一些自己需要的字段,并且同时可以使用auth模块提供的接口、方法。UserInfo表里就不需要有auth_user里重复的字段了,比如说username以及password等,但是还是可以直接使用这些字段的,并且django会自动将password进行加密
-----------------------------mdoels.py-------------------------
#继承AbstractUser
from django.contrib.auth.models import AbstractUser
class AuthUserInfo(AbstractUser):nid = models.AutoField(primary_key=True)phone_number = models.CharField(max_length=11,null=True,blank=True,unique=True)-----------------------------settings.py-------------------------------------
#配置settings
AUTH_USER_MODEL = "app01.AuthUserInfo"----------------------order----------------------------------------------------
python manage.py makemigrations
python manage.py migrate可以使用扩展表进行添加用户等功能---------------------------------------------------
AuthUserInfo.objects.create_user(username='用户名', password='密码')
7.Django文件上传
https://www.cnblogs.com/clschao/articles/10965144.html
8.Django文件下载
https://www.cnblogs.com/clschao/articles/10968864.html
9.使用django自带contentType表
说明:
contenttypes 是Django内置的一个应用,可以追踪项目中所有app和model的对应关系,并记录在ContentType表中。
models.py文件的表结构写好后,通过makemigrations和migrate两条命令迁移数据后,在数据库中会自动生成一个django_content_type表
应用场景:优惠劵
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelationclass Electrics(models.Model):name = models.CharField(max_length=32)price = models.IntegerField(default=100)coupons = GenericRelation(to='Coupon') # 用于反向查询,不会生成表字段def __str__(self):return self.nameclass Foods(models.Model):name = models.CharField(max_length=32)price=models.IntegerField(default=100)coupons = GenericRelation(to='Coupon')def __str__(self):return self.nameclass Clothes(models.Model):name = models.CharField(max_length=32)price = models.IntegerField(default=100)coupons = GenericRelation(to='Coupon')def __str__(self):return self.nameclass bed(models.Model):name = models.CharField(max_length=32)price = models.IntegerField(default=100)coupons = GenericRelation(to='Coupon')class Coupon(models.Model):"""Couponid name content_type_id object_id_id美的满减优惠券 9(电器表electrics) 3猪蹄买一送一优惠券 10 2南极被子买200减50优惠券 11 1"""name = models.CharField(max_length=32)content_type = models.ForeignKey(to=ContentType,on_delete=models.CASCADE) # step 1 既然没有直接和关联表进行外键关系,我们通过这一步先找到关联表object_id = models.PositiveIntegerField() # step 2 #存的是关联的那个表的对应的那条记录的idcontent_object = GenericForeignKey('content_type', 'object_id') # step 3 对象.content_object直接就拿到了这个优惠券对象关联的那个商品记录对象。def __str__(self):return self.name
总结: 当一张表和多个表FK关联,并且多个FK中只能选择其中一个或其中n个时,可以利用contenttypes app,只需定义三个字段就搞定!
10.DJANOG项目调试工具django-debug-toolbar
1.介绍:
django-debug-toolbar 是一组可配置的面板,可显示有关当前请求/响应的各种调试信息,并在单击时显示有关面板内容的更多详细信息。2.安装:
pip install django-debug-toolbar
配置:
----------------1.urls.py--------------------
from django.conf import settings
from django.conf.urls import include
if settings.DEBUG:import debug_toolbarurlpatterns = [re_path('__debug__/',include(debug_toolbar.urls)),]+ urlpatterns
----------------2.settings.py--------------------
MIDDLEWARE = ['debug_toolbar.middleware.DebugToolbarMiddleware',
]INSTALLED_APPS = ['debug_toolbar',
]#如果是本机调试,还在将127.0.0.1加入 INTERNAL_IPS在settings.py中加入以下配置项:
INTERNAL_IPS = [‘127.0.0.1’, ]#配置jQuery的URL
DEBUG_TOOLBAR_CONFIG = {"JQUERY_URL": '//cdn.bootcss.com/jquery/2.2.4/jquery.min.js',
}
11.Django的ORM性能
https://www.cnblogs.com/clschao/articles/11804565.html
1.能用values的尽量不查询对象,然后对象.属性的操作
2.select_related 主动连表,针对一对一或者外键
3.perfetch_related 子查询 ,针对一对一或者外键或者多对多
4.only只查询指定字段数据 defer排除某些字段
12.Django缓存和信号
缓存
问题:
由于Django是动态网站,所有每次请求均会去数据进行相应的操作,当程序访问量大时,耗时必然会更加明显
方案:
缓存将某个views的返回值保存至内存或者memcache中,5分钟内再有人来访问时,则不再去执行view中的操作,而是直接从内存或者memcache中之前缓存的内容拿到,并返回。
常用缓存方式:
1.Memcache缓存(python-memcached模块)
# 此缓存使用python-memcached模块连接memcacheCACHES = {'default': {'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache','LOCATION': '127.0.0.1:11211',}}CACHES = {'default': {'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache','LOCATION': 'unix:/tmp/memcached.sock',}} CACHES = {'default': {'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache','LOCATION': ['172.19.26.240:11211','172.19.26.242:11211',]}}2.Memcache缓存(pylibmc模块)# 此缓存使用pylibmc模块连接memcacheCACHES = {'default': {'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache','LOCATION': '127.0.0.1:11211',}}CACHES = {'default': {'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache','LOCATION': '/tmp/memcached.sock',}} CACHES = {'default': {'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache','LOCATION': ['172.19.26.240:11211','172.19.26.242:11211',]}}
3.django-redis 缓存pip install django-redisCACHES = {"default": {"BACKEND": "django_redis.cache.RedisCache","LOCATION": "redis://127.0.0.1:6379/1","OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient",}}}详细https://django-redis-chs.readthedocs.io/zh_CN/latest/
应用缓存
1.全站使用:
使用中间件,经过一系列的认证等操作,如果内容在缓存中存在,则使用FetchFromCacheMiddleware获取内容并返回给用户,当返回给用户之前,判断缓存中是否已经存在,如果不存在则UpdateCacheMiddleware会将缓存保存至缓存,从而实现全站缓存缓存多长时间等配置默认是按照你上面的配置部分来的MIDDLEWARE = ['django.middleware.cache.UpdateCacheMiddleware', #中间件第一个# 其他中间件...'django.middleware.cache.FetchFromCacheMiddleware', #中间件最后一个]CACHE_MIDDLEWARE_ALIAS = "" 用于存储的缓存别名。CACHE_MIDDLEWARE_SECONDS = "" 应该缓存每个页面的秒数CACHE_MIDDLEWARE_KEY_PREFIX = "" 如果使用同一django安装跨多个站点共享缓存,请将其设置为站点名称或此django实例唯一的其他字符串,以防止密钥冲突。如果你不在乎,就用空字符串
2.单个视图缓存:
方式一:from django.views.decorators.cache import cache_pageimport time@cache_page(5)def my_view(request):tm = time.time()...all_book = models.Book.objects.all()print(all_book) #也是5秒之后打印一次,因为5秒内都是从缓存中拿的,没有执行我们的视图函数,所有没有去数据库中取,注意,对实时性数据要求很高的,不要做缓存return render(request,'index.html',{'tm':tm }) # 当我们刷新页面的时候,你会发现tm在5秒之后才会发生变化方式二:from django.views.decorators.cache import cache_pageurlpatterns = [url(r'^foo/([0-9]{1,2})/$', cache_page(60 * 15)(my_view)), #装饰器原始用法]
信号
意义:Django中提供了“信号调度”,用于在框架执行操作时解耦。通俗来讲,就是一些动作发生的时候,信号允许特定的发送者去提醒一些接受者。
详细:https://docs.djangoproject.com/en/1.11/topics/signals/
13.Django中数据库主从复制,读写分离和一主多从的使用
配置:
1.-------------------------settings.py------------------------------------DATABASES = {'default': { #默认数据库,配置多个mysql数据也是ok的,混用数据库也是ok的'ENGINE': 'django.db.backends.sqlite3','NAME': os.path.join(BASE_DIR, 'db.sqlite3'),},'db2': { #配置的第二个数据库,注意数据库名字不能相同'ENGINE': 'django.db.backends.sqlite3','NAME': os.path.join(BASE_DIR, 'db2.sqlite3'),}
}
DATABASE_ROUTERS = ['app01.router.Router',] #写上面这个类的路径,我的是在app01应用文件夹下面的router.py文件中了2.-----------------------order---------------python migrate --database db2(库名)3.在应用文件夹中创建一个py文件,名字随意,比如叫做router.py文件
class Router:# 读操作用default库,就return这个库名字符串def db_for_read(self,model,**kwargs):return 'default'def db_for_write(self,model,**kwargs):return 'db2'
4.------------------------------------------一主多从------------------------
import random
class Router:# 读操作用default库,就return这个库名字符串def db_for_read(self,model,**kwargs):print(model)print(dir(model))# 其中有个_meta属性很有用a = model._meta.app_label #获取当前model对象所在的应用名称m = model._meta.model_name 获取当前操作的model对象的表名,也可以根据表名来进行多数据库读的分配# 可以根据应用选择不用的库来进行读取if a == 'app01':return 'db1'elif a == 'app02':return 'db2'return 'default'def db_for_write(self,model,**kwargs):
14.同源和跨域,CORS跨域通信实现和Jsonp跨域通信实现
同源
同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。其域名、协议、端口相同。
CORS通信实现跨域
整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。
浏览器将CORS请求分成两类:
1.简单请求(simple request)
(1) 请求方法是以下三种方法之一:(也就是说如果你的请求方法是什么put、delete等肯定是非简单请求)
HEAD
GET
POST
(2)HTTP的头信息不超出以下几种字段:(如果比这些请求头多,那么一定是非简单请求)
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain,也就是说,如果你发送的application/json格式的数据,那么肯定是非简单请求,vue的axios默认的请求体信息格式是json的,ajax默认是urlencoded的。
2.非简单请求(not-so-simple request)
凡是不同时满足简单请求的条件,就属于非简单请求。
3.简单请求和非简单请求的区别?
简单请求:一次请求非简单请求:两次请求,在发送数据之前会先发一次请求用于做“预检”,只有“预检”通过后才再发送一次请求用于数据传输。
* 关于“预检”- 请求方式:OPTIONS
- “预检”其实做检查,检查如果通过则允许传输数据,检查不通过则不再发送真正想要发送的消息
- 如何“预检”=> 如果复杂请求是PUT等请求,则服务端需要设置允许某请求,否则“预检”不通过Access-Control-Request-Method=> 如果复杂请求设置了请求头,则服务端需要设置允许某请求头,否则“预检”不通过Access-Control-Request-Headers
4.总结:
支持跨域,简单请求服务器设置响应头:Access-Control-Allow-Origin = '域名' 或 '*'
支持跨域,复杂请求由于复杂请求时,首先会发送“预检”请求,如果“预检”成功,则发送真实数据。“预检”请求时,允许请求方式则需服务器设置响应头:Access-Control-Request-Method“预检”请求时,允许请求头则需服务器设置响应头:Access-Control-Request-Headers
Jsonp实现跨域
原理:通过script标签的跨域特性来绕过同源策略。
<button onclick="f()">sendAjax</button><script>function addScriptTag(src){var script = document.createElement('script');script.setAttribute("type","text/javascript");script.src = src;document.body.appendChild(script);document.body.removeChild(script);}function func(name){ #接受返回数据的回调函数alert("hello"+name)}function f(){addScriptTag("http://127.0.0.1:8002/SendAjax/")}
</script>
Django完整版概述相关推荐
- 月结流程概述(加作者微信索取无水印PDF完整版)
http://blog.sina.com.cn/s/blog_eb52f4660102wl7c.html 1.1 月结流程概述(加作者微信索取无水印PDF完整版) 下表为典型的制造业期末财务结算 ...
- python实战一个完整的项目-Python项目开发实战(第2版)高清晰PDF完整版+代码
会写代码≠能做好项目! 1.建立有序生产环境 2.迅速融入开发团队 3.高效处理项目问题 网罗Python项目开发中的流程,让你的编程事半功倍 Python项目与封装/团队开发环境/问题驱动开发/源码 ...
- 那些珍贵的「视觉SLAM」课程资料总结(补充版/完整版)
文章目录 一 前言 二 干货 原文链接: 那些珍贵的「视觉SLAM」课程资料总结(补充版/完整版) 在公众号「3D视觉工坊」后台,回复「SLAM」,获得以下资源的完整下载链接. 一 前言 之前曾经总结 ...
- 计算机应用基础全套ppt,计算机应用基础全套PPT电子教案(完整版).ppt
计算机应用基础全套PPT电子教案(完整版) 计算机应用基础;第一节 计算机概述第二节 计算机系统第三节 认识微型计算机第四节 计算机中信息的表示;一.计算机的定义二.计算机的产生和发展三.计算机的特点 ...
- Jedis使用教程完整版
2019独角兽企业重金招聘Python工程师标准>>> 摘要:概述Jedis是Redis官方推荐的Java连接开发工具.要在Java开发中使用好Redis中间件,必须对Jedis熟悉 ...
- Asp.NET Core2.0 项目实战入门视频课程_完整版
END OR START? 看到这个标题,你开不开心,激不激动呢? 没错,.net core的入门课程已经完毕了.52ABP.School项目从11月19日,第一章视频的试录制,到今天完整版出炉,离不 ...
- 奇妙的安全旅行之加密算法(完整版)
hi,大家好,我是开发者FTD.之前我在公众号写了一个工作中常用加密算法系列的文章终于肝完了,为了方便小伙伴们查看和收藏,我将这个系列汇集整理成了一个PDF文档,有需要的小伙伴可以关注公众号,在公众号 ...
- Vue2+VueRouter2+webpack 构建项目实战系列(完整版) - 收录篇
以下为收录的CSDN博客专家 · 博文系列: Vue2+VueRouter2+Webpack+Axios 构建项目实战2017重制版(一)基础知识概述 Vue2+VueRouter2+Webpack+ ...
- WEB前端开发职业学习路线初级完整版
下面小编专门为广大web前端开发职业者汇总了学习路线初级完整版,其实web前端开发工程师可算是高福利,高薪水的职业了,所以现在学习web前端开发的技术人员也是越来越多了,但是在学习web前端开发中去学 ...
最新文章
- SSD(Single shot multibox detector)目标检测模型架构和设计细节分析
- 一分钟详解PCL-1.8.1从源码搭建开发环境四(VTK库的编译)
- Git高速入门——Git安装、创建版本号库以及经常使用命令
- 【首发】徐亦达团队新论文推荐:模限界矩阵分解
- Webdriver使用Chrome模拟手机浏览器测试移动版网站
- 远程Linux主机安装zsh插件zsh-syntax-highlighting
- c++直角坐标系与极坐标系的转换_平面向量的奇技淫巧——斜坐标系的一系列低级研究...
- es6 字符串的 Iterator 接口
- USACO / Factorials (简单模拟)
- 大学中计算机考查课不及格怎么办,大学体测不及格怎么办 有哪些补救方法
- 查看服务器大文件,linux 查看服务器大文件
- 美研究人员首次验证声波可让光纤中的光变“听话”
- 我的spark学习之路(一)
- BLDC无刷直流电机的原理及驱动基础
- python抢点_零基础SQL小白入门学习路线与书单
- 手把手QQ机器人制作教程,根据官方接口进行开发,基于Python语言制作的详细教程(更新中)
- 在 Beagleboard-x15 上配置 spi 和 GPIO
- CCS安装失败的原因
- 电力系统非线性控制_第二届电气,控制,自动化和机器人国际学术会议 (ECAR2020)...
- CPU和CPU寄存器和CPU缓存和CPU内存管理器、RAM、hard disk。以及堆栈、内存映射。
热门文章
- Mtk android中的设备树,msm8909+android5.1 device tree(dt) 设备树组成和编译
- 平台优势突出!科东软件被评定为广州开发区2020年工业互联网服务商
- s805b刷Linux系统,如何在机顶盒中刷入原生的Debian系统
- 【第十四题】水晶球|贪心(北理工/北京理工大学/程序设计方法与实践/小学期 )
- 王老师讲解:SQL Server 2005数据库nolock的使用【来自Aisino_BBS】
- yolov5环境配置和训练
- 《华尔街日报》遭黑客攻击 读者数据库被盗
- selenium如何定位span元素
- ubuntu下docker部署jeecgboot
- Makefile介绍