目录
- 简介
- MVC与MTV模型
- MVC
- MTV
- 创建项目
- 目录
- 生命周期
- 静态文件配置(无用)
- 启动django[启动](https://www.cnblogs.com/xiaoyuanqujing/articles/11902303.html)
- 路由
- 分组
- 无名分组
- 有名分组
- 路由分发
- 反向解析
- 反向解析结合分组
- 名称空间
- re_path与path
- 自定义转换器
- 视图
- HttpRequest
- 常用方法
- HttpResponse
- Json
- FBV和CBV
- 模板(前后端分离不用看)
- 变量
- 过滤器
- 循环
- 分支
- csrf
- with
- 自定义过滤器和标签
- 模板的导入和继承
- 模板的继承\派生之extends标签、block标签
- inclusion_tag
- ORM模型层
- 单表操作
- 创建models.py
- 配置数据库连接
- 在链接mysql数据库前,必须事先创建好数据库,下载连接
- 配置APP
- 打印sql-orm
- 迁移数据库
- 修改字段
- 操作记录
- 新增
- 查询
- QuerySet对象:
- 链式处理:
- 其他查询API:
- 双下划线查询
- F与Q查询
- 聚合
- 分组
- 更新
- 删除
简介
MVC与MTV模型
MVC
MVC就是把Web应用分为模型(M),控制器©和视图(V)三层,他们之间以一种插件式的、松耦合的方式连接在一起,模型负责业务对象与数据库的映射(ORM),视图负责与用户的交互(页面),控制器接受用户的输入调用模型和视图完成用户的请求。
MTV
Django的MTV模式本质上和MVC是一样的,也是为了各组件间保持松耦合关系,只是定义上有些许不同,Django的MTV分别是值:
- M 代表模型(Model): 负责业务对象和数据库的关系映射(ORM)。
- T 代表模板 (Template):负责如何把页面展示给用户(html)。
- V 代表视图(View): 负责业务逻辑,并在适当时候调用Model和Template。
除了以上三层之外,还需要一个URL分发器,它的作用是将一个个URL的页面请求分发给不同的View处理,View再调用相应的Model和Template。
创建项目
# 在命令行执行以下指令,会在当前目录生成一个名为appserver的文件夹,该文件夹中包含Django框架的一系列基础文件
django-admin startproject appserver
# 切换到appserver目录下边
cd mysite
# 创建功能模块user,此处的startapp代表创建application下的一个功能模块。
python manage.py startapp user
# 运行http://127.0.0.1:8000
python manage.py runserver 8000
目录
-manage.py---项目入口,执行一些命令
-项目名
-settings.py 全局配置信息
-urls.py 总路由
-app名字
-migrations 数据库迁移的记录
-models.py 数据库表模型
-views.py 视图函数
生命周期
1、路由层(根据不同的地址执行不同的视图函数,详见urls.py)
2、视图层(定义处理业务逻辑的视图函数,详见views.py)
3、模型层 (跟数据库打交道的,详解models.py)
4、模板层(待返回给浏览器的html文件,详见templates)
静态文件配置(无用)
对于前后端分离的项目,静态文件一般用nginx代理,不用特意在webServer框架再配置
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static'),
]
前端对应配置:
# 加载静态目录
{% load static %}
<script src="{% static 'js/jQuery3.6.0.js' %}"></script>
<script src="{% static 'js/bootstrap.min.js' %}"></script>
<script src="{% static 'js/sweetalert.min.js' %}"></script>
<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
<link rel="shortcut icon" href="{% static "img/favicon.ico" %}">
模板语法:
{{}}用于取值,{% %}用于逻辑
启动django启动
-
manage.py是入口,执行了
execute_from_command_line(sys.argv)
-
进入management_init_.py,执行了:
cmd : python manage.py runserver 8000 argv: runserver 8000 def execute_from_command_line(argv=None): """Run a ManagementUtility.""" utility = ManagementUtility(argv) utility.execute()
-
execute()方法中校验参数正误,并且执行:
self.fetch_command("runserver").run_from_argv(self.argv)
-
fetch_command(“runserver”)找到了对应的runserver模块的命令:
-
执行
CommandObj.run_from_argv(self.argv)
,再跳到self.execute(*args, **cmd_options)
,再获取到output = self.handle(*args, **options)
这个handler是runserver.handle() -
检查ip等信息后,执行
self.run(**options)
,最终执行了好几个类的run方法后到了最后一个run:
def run(addr, port, wsgi_handler, ipv6=False, threading=False, server_cls=WSGIServer):
server_address = (addr, port)
if threading:
httpd_cls = type('WSGIServer', (socketserver.ThreadingMixIn, server_cls), {})
else:
httpd_cls = server_cls
# 它是WSGI服务器与django之间相互通信的唯一枢纽通道,当WSGI服务对象收到socket请求后,会将这个请求传递给django
httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6)
if threading:
# ThreadingMixIn.daemon_threads indicates how threads will behave on an
# abrupt shutdown; like quitting the server by the user or restarting
# by the auto-reloader. True means the server will not wait for thread
# termination before it quits. This will make auto-reloader faster
# and will prevent the need to kill the server manually if a thread
# isn't terminating correctly.
httpd.daemon_threads = True
httpd.set_app(wsgi_handler)
# 启动非堵塞网络监听服务
httpd.serve_forever()
路由
# urls.py
from django.conf.urls import url
urlpatterns = [
url(regex, view, kwargs=None, name=None),
# 一个注册接口
url(r'^register/', account.register, name='register'),
]
#函数url关键参数介绍
# regex:正则表达式,用来匹配url地址的路径部分,
# view:通常为一个视图函数,用来处理业务逻辑
# kwargs:有名分组
# name:反向解析
-
django的路由匹配是自上而下的迭代,匹配到一个规则就不再向下,而gin框架则是利用前缀树,时间复杂度更小。
-
因此,django的首页配置要放到最后。
... # 充值页面 url('^magic/', order.pay, name='pay'), url('^order/', order.order, name='order'), url('^result/', order.pay_result, name='result'), url(r'^', home.index, name='index'), ]
-
路由以
/
结尾,不输入/
也能匹配到的原因:在配置文件settings.py中有一个参数APPEND_SLASH,该参数有两个值True或False,当APPEND_SLASH=True(如果配置文件中没有该配置,APPEND_SLASH的默认值为True),并且用户请求的url地址的路径部分不是以 / 结尾,Django会拿着路径部分(即index)去路由表中匹配正则表达式,发现匹配不成功,那么Django会在路径后加/
再去路由表中匹配,如果匹配失败则会返回路径未找到,如果匹配成功,则会返回重定向信息给浏览器。django的全局配置在(from django.conf import settings)
分组
适用于:http://127.0.0.1:8000/article/id/
,id为某些记录的主键
我还是喜欢用http://127.0.0.1:8000/article?id=id
这种restfulapi的形式QAQ
注意:分组是正则表达式的功能。
无名分组
# 路由
url(r'^aritcle/(\d+)/$',views.article),
# 视图函数需要配套接收,匹配成功会调用article(request,id)
def article(request,article_id):
return HttpResponse('id为 %s 的文章内容...' %article_id)
有名分组
url(r'^aritcle/(?P<article_id>\d+)/$',views.article)
# 视图函数需要配套接收,匹配成功会调用article(request,article_id=id)
def article(request,article_id):
return HttpResponse('id为 %s 的文章内容...' %article_id)
路由分发
- 每个功能模块应该有自己的urls.py文件。
- urls过多会导致路由匹配速度下降。
from django.conf.urls import url,include
from django.contrib import admin
# 总路由表
urlpatterns = [
url(r'^admin/', admin.site.urls),
# 新增两条路由,注意不能以$结尾。
# include函数就是做分发操作的。
# 总路由里面写前缀即可。
# app01.urls是因为app01已经在settings中注册了,否则无法获取app01/urls.py
url(r'^app01/', include('app01.urls')),
url(r'^app02/', include('app02.urls')),
]
或
from app01 import urls as app01_urls
from app02 import urls as app02_urls
url(r'^app01/', include(app01_urls)),
url(r'^app02/', include(app02_urls)),
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'user', # apps目录已经被加到环境变量了,直接能找到user
'home',
'course',
'order',
]
urlpatterns = [
path('xadmin/', xadmin.site.urls),
re_path('media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT}),
path('home/', include('home.urls')),
path('user/', include('user.urls')),
path('course/', include('course.urls')),
path('order/', include('order.urls'))
]
反向解析
url(r'^register/', account.register, name='register'),
编写一条url(regex, view, kwargs=None, name=None)时,可以通过参数name为url地址的路径部分起一个别名,项目中就可以通过别名来获取这个路径
from django.shortcuts import render
from django.shortcuts import reverse # 用于反向解析
from django.shortcuts import redirect #用于重定向页面
from django.shortcuts import HttpResponse
def login(request):
if request.method == 'GET':
# 当为get请求时,返回login.html页面,页面中的{% url 'login_page' %}会被反向解析成路径:/login/
return render(request, 'login.html')
url = reverse('index_page') # reverse会将别名'index_page'反向解析成路径:/index/
return redirect(url) # 重定向到/index/
# 前端
<a href="{% url 'index' %}"><img src="{% static 'img/head.png' %}" alt="" style="width: 100px"></a>
反向解析结合分组
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^aritcle/(\d+)/$',views.article,name='article_page'), # 无名分组
url(r'^user/(?P<uid>\d+)/$',views.article,name='user_page'), # 有名分组
]
# 1 针对无名分组
在views.py中,反向解析的使用:
url = reverse('article_page',args=(1,))
在模版login.html文件中,反向解析的使用
{% url 'article_page' 1 %}
# 2 针对有名分组
在views.py中,反向解析的使用:
url = reverse('user_page',kwargs={'uid':1})
在模版login.html文件中,反向解析的使用
{% url 'user_page' uid=1 %}
名称空间
如果别名存在重复,那么在反向解析时则会出现覆盖。(推荐给路由起名字时按:name=“模块_功能_view”,一般不会重复)
# 总路由表
urlpatterns = [
url(r'^admin/', admin.site.urls),
# 传给include功能一个元组,元组的第一个值是路由分发的地址,第二个值则是名称空间起的名字
url(r'^app01/', include(('app01.urls','app01'))),
url(r'^app02/', include(('app02.urls','app02'))),
]
# 视图
from django.shortcuts import render
from django.shortcuts import HttpResponse
from django.shortcuts import reverse
def index(request):
url=reverse('app01:index_page') # 解析的是名称空间app01下的别名'index_page'
return HttpResponse('app01的index页面,反向解析结果为%s' %url)
1、在视图函数中基于名称空间的反向解析,用法如下
url=reverse('名称空间的名字:待解析的别名')
2、在模版里基于名称空间的反向解析,用法如下
<a href="{% url '名称空间的名字:待解析的别名'%}">哈哈</a>
re_path与path
- Django2.0中的re_path与django1.0的url一样。
- 在Django2.0中新增了一个path功能,用来解决:数据类型转换问题与正则表达式冗余问题:
urlpatterns = [
# 问题一:数据类型转换
# 正则表达式会将请求路径中的年份匹配成功然后以str类型传递函数year_archive,在函数year_archive中如果想以int类型的格式处理年份,则必须进行数据类型转换
re_path(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
# 问题二:正则表达式冗余
# 下述三个路由中匹配article_id采用了同样的正则表达式,重复编写了三遍,存在冗余问题,并且极不容易管理,因为一旦article_id规则需要改变,则必须同时修改三处代码
re_path(r'^article/(?P<article_id>[a-zA-Z0-9]+)/detail/$', views.detail_view),
re_path(r'^articles/(?P<article_id>[a-zA-Z0-9]+)/edit/$', views.edit_view),
re_path(r'^articles/(?P<article_id>[a-zA-Z0-9]+)/delete/$', views.delete_view),
]
---------------------------------------------------------
urlpatterns = [
# 问题一的解决方案:
path('articles/<int:year>/', views.year_archive), # <int:year>相当于一个有名分组,其中int是django提供的转换器,相当于正则表达式,专门用于匹配数字类型,而year则是我们为有名分组命的名,并且int会将匹配成功的结果转换成整型后按照格式(year=整型值)传给函数year_archive
# 问题二解决方法:用一个int转换器可以替代多处正则表达式
path('articles/<int:article_id>/detail/', views.detail_view),
path('articles/<int:article_id>/edit/', views.edit_view),
path('articles/<int:article_id>/delete/', views.delete_view),
]
#1、path与re_path或者1.0中的url的不同之处是,传给path的第一个参数不再是正则表达式,而是一个完全匹配的路径,相同之处是第一个参数中的匹配字符均无需加前导斜杠
#2、使用尖括号(<>)从url中捕获值,相当于有名分组
#3、<>中可以包含一个转化器类型(converter type),比如使用 <int:name> 使用了转换器int。若果没有转化器,将匹配任何字符串,当然也包括了 / 字符
默认支持五种转换器
1. str,匹配除了路径分隔符(/)之外的非空字符串,这是默认的形式
2. int,匹配正整数,包含0。
3. slug,匹配字母、数字以及横杠、下划线组成的字符串。
4. uuid,匹配格式化的uuid,如 075194d3-6885-417e-a8a8-6c931e272f00。
5. path,匹配任何非空字符串,包含了路径分隔符(/)
自定义转换器
模板如下:
class MonthConverter:
# 正则
# 属性名必须为regex
regex='\d{2}'
# to_python用于将参数类型转换后传递到视图函数
def to_python(self, value):
return int(value)
# to_url用于url反向引用
def to_url(self, value):
# # 匹配的regex是两个数字,返回的结果也必须是两个数字
return value
视图
视图函数,简称视图,属于Django的视图层,默认定义在views.py文件中,是用来处理web请求信息以及返回响应信息的函数,所以研究视图函数只需熟练掌握两个对象即可:请求对象(HttpRequest)和响应对象(HttpResponse)
HttpRequest
常用方法
个人喜欢Form(data=request.POST)直接导入form,方便校验
一.HttpRequest.method
获取请求使用的方法(值为纯大写的字符串格式)。例如:"GET"、"POST"
二.HttpRequest.GET
值为一个类似于字典的QueryDict对象,封装了GET请求的所有参数,可通过HttpRequest.GET.get('键')获取相对应的值
三.HttpRequest.POST
值为一个类似于字典的QueryDict对象,封装了POST请求所包含的表单数据,可通过HttpRequest.POST.get('键')获取相对应的值
针对表单中checkbox类型的input标签、select标签提交的数据,键对应的值为多个,需要用:HttpRequest.POST.getlist("hobbies")获取存有多个值的列表,同理也有HttpRequest.GET.getlist("键")
四.HttpRequest.body
ajax可以提交的数据格式有:1、编码格式1 2、编码格式2 3、json,当ajax采用POST方法提交前两种格式的数据时,django的处理方案同上,但是当ajax采用POST方法提交json格式的数据时,django会将接收到的数据存放于HttpRequest.body,此时需要我们自己对HttpRequest.body属性值做反序列化操作。
五.HttpRequest.FILES
HttpRequest.FILES 只有在请求的方法为POST 且提交的<form> 带有enctype="multipart/form-data" 的情况下才会包含数据。否则,FILES 将为一个空的类似于字典的对象。该属性值为一个类似于字典的对象,可以包含多组key:value(对应多个上传的文件)class MultiValueDict(dict). [filename1:[obj1,obj2]]
上传文件:
with open(filePath,"wb") as writer:
for line in request.FILES.get("filename"):
writer.write(line)
六.HttpRequest.path
获取url地址的路径部分,只包含路径部分
七.HttpRequest.get_full_path()
获取url地址的完整path,既包含路径又包含参数部分
http://127.0.0.1:8001/order/?name=egon&age=10
HttpRequest.path的值为"/order/"
HttpRequest.get_full_path()的值为"/order/?name=egon&age=10"
八.HttpRequest.META
值为包含了HTTP协议的请求头数据的Python字典,字典中的key及期对应值的解释如下
CONTENT_LENGTH —— 请求的正文的长度(是一个字符串)。
CONTENT_TYPE —— 请求的正文的MIME类型。
HTTP_ACCEPT —— 响应可接收的Content-Type。
HTTP_ACCEPT_ENCODING —— 响应可接收的编码。
HTTP_ACCEPT_LANGUAGE —— 响应可接收的语言。
HTTP_HOST —— 客服端发送数据的目标主机与端口
HTTP_REFERER —— Referring 页面。
HTTP_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 的键时,
都会
1、将所有字母大写
2、将单词的连接符替换为下划线
3、加上前缀HTTP_。
所以,一个叫做 X-Bender 的头部将转换成 META 中的 HTTP_X_BENDER 键。
九.HttpRequest.COOKIES
一个标准的Python字典,包含所有的cookie。键和值都为字符串。
十.HttpRequest.session
一个既可读又可写的类似于字典的对象,表示当前的会话。只有当Django 启用会话的支持时才可用。
十一.HttpRequest.user(用户认证组件下使用)
一个 AUTH_USER_MODEL 类型的对象,表示当前登录的用户,也可以自定义为某User对象。
十二.HttpRequest.is_ajax()
如果请求是通过XMLHttpRequest 发起的,则返回True,方法是检查 HTTP_X_REQUESTED_WITH 相应的首部是否是字符串'XMLHttpRequest'。
HttpResponse
响应可以是任何形式的内容,比如一个HTML文件的内容,一个重定向,一个404错误,一个XML文档,或者一张图片等。
快捷方式:
# 前后端不分离
from django.shortcuts import HttpResponse,render,redirect
# json
from django.http import JsonResponse
# 实际上返回文件也不应该用最基本的HttpResonse()
# def __init__(self, content=b'', *args, **kwargs):
# 参数传入centent就行
return HttpResponse(png)
return render(request, 'register.html', locals(), status=200)
def render(request, template_name, context=None, content_type=None, status=None, using=None):
"""
1. request:request对象
2. template_name:模板名字(利用配置文件里的templates去反射文件)
3. context: 本次请求上下文数据,可以传入locals()自动将数据导入
4. content_type: http协议的Content-Type
5. status: http协议的状态码
6. using: ...
"""
content = loader.render_to_string(template_name, context, request, using=using)
return HttpResponse(content, content_type, status)
return redirect('/some/url/')
def redirect(to, *args, permanent=False, **kwargs):
"""
1. to: 目标url
2. permanent: 是否永久,决定状态码是301还是302
301表示旧地址A的资源已经被永久地移除了,即这个资源不可访问了。搜索引擎在抓取新内容的同时也将旧的网址转换为重定向之后的地址;
302表示旧地址A的资源还在,即这个资源仍然可以访问,这个重定向只是临时地从旧地址A跳转到地址B,搜索引擎会抓取新的内容、并且会保存旧的网址。 从SEO层面考虑,302要好于301.
"""
redirect_class = HttpResponsePermanentRedirect if permanent else HttpResponseRedirect
return redirect_class(resolve_url(to, *args, **kwargs))
Json
from django.http import JsonResponse
def my_view(request):
data=['fuck','off']
return JsonResponse(data,safe=False)
# 默认safe=True代表只能序列化字典对象,safe=False代表可以序列化字典以外的对象
def __init__(self, data, encoder=DjangoJSONEncoder, safe=True,
json_dumps_params=None, **kwargs):
if safe and not isinstance(data, dict):
raise TypeError(
'In order to allow non-dict objects to be serialized set the '
'safe parameter to False.'
)
if json_dumps_params is None:
json_dumps_params = {}
kwargs.setdefault('content_type', 'application/json')
data = json.dumps(data, cls=encoder, **json_dumps_params)
super().__init__(content=data, **kwargs)
json_dumps_params可以传入一些指定参数,如:防止将中文字符串转为ascii,ensure_ascii:False
,
传入的json_dumps_params会被django内置的json序列化方法捕获到使用。
FBV和CBV
django的视图层由两种形式构成:FBV和CBV
1、FBV基于函数的视图(Function base view),我们之前一直介绍的都是FBV
2、CBV基于类的视图(Class base view)
其实CBV有FBV并无太大差异,视图类在经过as_view()和dispatch()后就变成了视图函数。
from django.urls import path,re_path
from app01 import views
urlpatterns = [
# 必须调用类下的方法as_view
re_path(r'^login/',views.LoginView.as_view()),
]
from django.shortcuts import render,HttpResponse,redirect
from django.views import View
class LoginView(View):
# 菜鸟如我一般不用重写
def dispatch(self, request, *args, **kwargs):
# 可在该方法内做一些预处理操作
# 必须继承父类的dispatch功能
# 当请求url为:http://127.0.0.1:8008/login/会先触发dispatch的执行
# 如果http协议的请求方法为GET,则调用下述get方法
# 如果http协议的请求方法为POST,则调用下述post方法
return super().dispatch(request, *args, **kwargs)
# 会被反射request.method取到
def get(self,request):
return render(request,'login.html')
# # 会被反射request.method取到
def post(self,request):
return HttpResponse(res)
原生的as_view()和dispatch()方法比较简单:
def dispatch(self, request, *args, **kwargs):
if request.method.lower() in ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']:
# 反射获取视图函数
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
# 默认method_not_allowed 403 视图函数
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs)
@classonlymethod
def as_view(cls, **initkwargs):
# 略过
for key in initkwargs:
if key in cls.http_method_names:
raise TypeError("You tried to pass in the %s method name as a "
"keyword argument to %s(). Don't do that."
% (key, cls.__name__))
if not hasattr(cls, key):
raise TypeError("%s() received an invalid keyword %r. as_view "
"only accepts arguments that are already "
"attributes of the class." % (cls.__name__, key))
# 核心
def view(request, *args, **kwargs):
# self = 视图类()
self = cls(**initkwargs)
if hasattr(self, 'get') and not hasattr(self, 'head'):
self.head = self.get
# 赋值request和参数,可以通过self.request获取request对象了
self.setup(request, *args, **kwargs)
# 刚赋值,怎么可能取不到呢?除非你手贱重写了setup()
if not hasattr(self, 'request'):
# 询问你是否手贱重写了setup()
raise AttributeError(
"%s instance has no 'request' attribute. Did you override "
"setup() and forget to call super()?" % cls.__name__
)
# 调用dispatch()
return self.dispatch(request, *args, **kwargs)
# 给view赋值,伪装
view.view_class = cls
view.view_initkwargs = initkwargs
# take name and docstring from class
update_wrapper(view, cls, updated=())
# and possible attributes set by decorators
# like csrf_exempt from dispatch
update_wrapper(view, cls.dispatch, assigned=())
# 返回装饰器后的view
return view
模板(前后端分离不用看)
django的模板=HTML代码+模板语法
一、变量:{{ 变量名 }}
1.1 深度查询:句点符的应用
1.2 过滤器
二、标签:{% 标签名 %}
三、自定义标签和过滤器
四、模板的导入和继承
变量
return render(request,'test.html',{'msg':msg,'dic':dic,'obj':obj,'li':li})
或
#locals()会将函数内定义的名字与值转换为字典中的k与v
return render(request,'test.html',locals())
<p>{{ msg }}</p>
<p>{{ dic }}</p>
<p>{{ obj }}</p>
<p>{{ li }}</p>
<!--调用字符串对象的upper方法,注意不要加括号-->
<p>{{ msg.upper }}</p>
<!--取字典中k1对应的值-->
<p>{{ dic.k1 }}</p>
<!--取对象的name属性-->
<p>{{ obj.name }}</p>
<!--取列表的第2个元素,然后变成大写-->
<p>{{ li.1.upper }}</p>
<!--取列表的第3个元素,并取该元素的age属性-->
<p>{{ li.2.age }}</p>
过滤器
可以理解为Linux的管道符,将前面的结果传给后面运算
{{ 变量名|过滤器名:传给过滤器的参数 }}
#1、default
#作用:如果一个变量值是False或者为空,使用default后指定的默认值,否则,使用变量本身的值
{{ value|default:"nothing" }}
#2、length
#作用:返回值的长度。它对字符串、列表、字典等容器类型都起作用
{{ value|length }}
#3、filesizeformat
#作用:将值的格式化为一个"人类可读的"文件尺寸(如13KB、4.1 MB、102bytes等等)
{{ value|filesizeformat }}
#4、date
#作用:将日期按照指定的格式输出,如果value=datetime.datetime.now(),按照格式Y-m-d则输出2019-02-02
{{ value|date:"Y-m-d" }}
#5、slice
#作用:对输出的字符串进行切片操作,顾头不顾尾
{{ value|slice:"0:2" }}
#6、truncatechars
#作用:如果字符串字符多于指定的字符数量,那么会被截断。截断的字符串将以可翻译的省略号序列(“...”)结尾,注意8个字符也包含末尾的3个点
{{ value|truncatechars:8 }}
#7、truncatewords
#作用:同truncatechars,但truncatewords是按照单词截断,注意末尾的3个点不算作单词,
{{ value|truncatewords:2 }}
#8、safe
#作用:出于安全考虑,Django的模板会对HTML标签、JS等语法标签进行自动转义,例如value="<script>alert(123)</script>",模板变量{{ value }}会被渲染成<script>alert(123)</script>交给浏览器后会被解析成普通字符”<script>alert(123)</script>“,失去了js代码的语法意义,但如果我们就想让模板变量{{ value }}被渲染的结果有语法意义,那么就用到了过滤器safe,比如value='<a href="https://www.baidu.com">点我啊</a>',在被safe过滤器处理后就成为了真正的超链接,不加safe过滤器则会当做普通字符显示’<a href="https://www.baidu.com">点我啊</a>‘
{{ value|safe }}
过滤器 | 描述 | 示例 |
---|---|---|
upper | 以大写方式输出 | {{ user.name | upper }} |
add | 给value加上一个数值 | {{ user.age | add:”5” }} |
addslashes | 单引号加上转义号 | {{ val | addslashes }} |
capfirst | 第一个字母大写 | {{ ‘good’| capfirst }} 返回”Good” |
center | 输出指定长度的字符串,把变量居中 | {{ “abcd”| center:”50” }} |
cut | 删除指定字符串 | {{ “You are not a Englishman” | cut:”not” }} |
default_if_none | 如果值为None, 则使用默认值代替 | {{ val | default_if_none:"default" }} |
dictsort | 按某字段排序,变量必须是一个dictionary | {% for moment in moments | dictsort:”id” %} |
dictsortreversed | 按某字段倒序排序,变量必须是dictionary | {% for moment in moments | dictsortreversed:”id” %} |
divisibleby | 判断是否可以被数字整除 | {{ 224 | divisibleby:10}} |
escape | 按HTML转义,比如将”<”转换为”<” | {{ html | escape }} |
first | 返回列表的第1个元素,变量必须是一个列表 | {{ list | first }} |
floatformat | 转换为指定精度的小数,默认保留1位小数 | {{ 3.1415926 | floatformat:3 }} 返回 3.142 四舍五入 |
get_digit | 从个位数开始截取指定位置的数字 | {{ 123456 | get_digit:’1’}} |
join | 用指定分隔符连接列表 | {{ [‘abc’,’45’] | join:’’ }} 返回 abc45 |
length_is | 检查列表,字符串长度是否符合指定的值 | {{ ‘hello’| length_is:’3’ }} |
linebreaks | 用p标签或br标签包裹变量 | {{ “Hi\n\nDavid”|linebreaks }} 返回 Hi David |
linebreaksbr | 用br标签代替换行符 | {{ hi\r\n\r\n | linebreaksbr }} |
linenumbers | 为变量中的每一行加上行号 | {{ val | linenumbers }} |
ljust | 输出指定长度的字符串,变量左对齐 | {{‘ab’|ljust:5}}返回 ‘ab ’ |
lower | 字符串变小写 | {{ "AB" | lower }} |
make_list | 将字符串转换为列表 | {{ "150" | make_list }} |
random | 返回列表的随机一项 | {{ list | random }} |
removetags | 删除字符串中指定的HTML标记 | {{value | removetags: “h1 h2”}} |
rjust | 输出指定长度的字符串,变量右对齐 | {{‘ab’|rjust:5}}返回 ‘ab ’ |
slugify | 在字符串中留下减号和下划线,其它符号删除,空格用减号替换 | {{ '5-2=3and5 2=3' }} |
time | 返回日期的时间部分 | {{ time_str | time }} |
truncatewords_html | 保留其中的HTML标签 | {{ 'p This is a pen p' | truncatewords_html: 5 }} |
urlencode | 将字符串中的特殊字符转换为url兼容表达方式 | {{ ‘http://www.aaa.com/foo?a=b&b=c’ | urlencode}} |
urlize | 将变量字符串中的url由纯文本变为链接 | {{ url | urlize }} |
yesno | 将布尔变量转换为字符串yes, no 或maybe | {{ True | yesno }} |
循环
#1、遍历每一个元素:
{% for person in person_list %}
<p>{{ person.name }}</p>
{% endfor %}
#2、可以利用{% for obj in list reversed %}反向循环。
#3、遍历一个字典:
{% for key,val in dic.items %}
<p>{{ key }}:{{ val }}</p>
{% endfor %}
#4、循环序号可以通过{{ forloop }}显示
{% for name in names %}
<p>{{ forloop.counter0 }} {{ name }}</p>
{% endfor %}
forloop.counter 当前循环的索引值(从1开始)
forloop.counter0 当前循环的索引值(从0开始)
forloop.revcounter 当前循环的倒序索引值(从1开始)
forloop.revcounter0 当前循环的倒序索引值(从0开始)
forloop.first 当前循环是第一次循环则返回True,否则返回False
forloop.last 当前循环是最后一次循环则返回True,否则返回False
forloop.parentloop 本层循环的外层循环
#5、for标签可以带有一个可选的{% empty %} 从句,在变量person_list为空或者没有被找到时,则执行empty子句
{% for person in person_list %}
<p>{{ person.name }}</p>
{% empty %}
<p>sorry,no person here</p>
{% endfor %}
分支
# 1、注意:
{% if 条件 %}条件为真时if的子句才会生效,条件也可以是一个变量,if会对变量进行求值,在变量值为空、或者视图没有为其传值的情况下均为False
# 2、具体语法
{% if num > 100 or num < 0 %}
<p>无效</p>
{% elif num > 80 and num < 100 %}
<p>优秀</p>
{% else %}
<p>凑活吧</p>
{% endif %}
#3、if语句支持 and 、or、==、>、<、!=、<=、>=、in、not in、is、is not判断。
csrf
前后端不分离需要在表单加入{% csrf_token %}
<form>
{% csrf_token %}
</form>
# 1、在GET请求到form表单时,标签{% csrf_token%}会被渲染成一个隐藏的input标签,该标签包含了由服务端生成的一串随机字符串,如<input type="hidden" name="csrfmiddlewaretoken" value="dmje28mFo...OvnZ5">
# 2、在使用form表单提交POST请求时,会提交上述随机字符串,服务端在接收到该POST请求时会对比该随机字符串,对比成功则处理该POST请求,否则拒绝,以此来确定客户端的身份
with
# with标签用来为一个复杂的变量名起别名,如果变量的值来自于数据库,在起别名后只需要使用别名即可,无需每次都向数据库发送请求来重新获取变量的值
{% with li.1.upper as v %}
{{ v }}
{% endwith %}
自定义过滤器和标签
- 将app注册到settings.py
- 在文件夹app中创建子文件夹templatetags(文件夹名只能是templatetags)
- 创建Py 文件,自定义过滤器和标签
from django import template
# 注意变量名必须为register,不可改变
register = template.Library()
#1、自定义过滤器
@register.filter
# 自定义的过滤器只能定义最多两个参数,针对{{ value1 | filter_multi:value2 }}
# 参数传递为v1=value1,v2=value2
def my_multi_filter(v1 ,v2):
return v1 * v2
#2、自定义标签
@register.simple_tag
# # 自定义的标签可以定义多个参数
def my_multi_tag(v1, v2):
return v1 * v2
#3、自定义标签扩展之mark_safe
# 注释:用内置的标签safe来让标签内容有语法意义,如果想让自定义标签处理的结果也有语法意义,则不能使用内置标签safe了,需要使用mark_safe,可以实现与内置标签safe同样的功能
from django.utils.safestring import mark_safe
@register.simple_tag
def my_input_tag(id, name):
res = "<input type='text' id='%s' name='%s' />" % (id, name)
return mark_safe(res)
- 使用
<!--必须先加载存有自定义过滤器和标签的文件-->
{% load inclusion_tags %}
# 过滤器
<!--salary的值为10,经过滤器my_multi_filter的处理结果为120-->
{{ salary|my_multi_filter:12 }}
# 标签
<!--结果为2-->
{% my_multi_tag 1 2 %}
# 标签
结果为一个input标签,该表的属性id="inp1" name="username"
注意:input的属性值均为字符串类型,所以my_input_tag后的两个值均为字符串类型
{% my_input_tag "inp1" "username" %}
# 对比
#1、自定义过滤器只能传两个参数,而自定义标签却可以传多个参数
#2、过滤器可以用于if判断,而标签不能
{% if salary|my_multi_filter:12 > 200 %}
<p>优秀</p>
{% else %}
<p>垃圾</p>
{% endif %}
模板的导入和继承
模板文件彼此之间可能会有大量冗余代码,为此django提供了专门的语法来解决这个问题,主要围绕三种标签的使用:include标签、extends标签、block标签。
#作用:在一个模板文件中,引入/重用另外一个模板文件的内容,
{% include '模版名称' %}
<div class="row">
<div class="col-md-3">
<!--在base.html引入advertise.html文件的内容-->
{% include "advertise.html" %}
</div>
<div class="col-md-9"></div>
</div>
模板的继承\派生之extends标签、block标签
#作用:在一个模板文件中,引入/重用另外一个模板文件的内容
# include有的功能extends全都有
# extends可以搭配一个block标签,用于在继承的基础上定制新的内容
{% extends "模版名称" %}
# 定义base.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block tilte %}{% endblock %}</title>
{% load static %}
<script src="{% static 'js/jQuery3.6.0.js' %}"></script>
<script src="{% static 'js/bootstrap.min.js' %}"></script>
<script src="{% static 'js/sweetalert.min.js' %}"></script>
<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
<link rel="shortcut icon" href="{% static "img/favicon.ico" %}" />
<style>
.navbar-default{
border-radius: 0;
}
.account{
width: 400px;
margin-top: 30px;
margin-left: auto;
margin-right: auto;
border: 1px solid #f0f0f0;
padding: 10px 30px 30px 30px;
-webkit-box-shadow: 5px 10px 10px rgba(0,0,0,.05);
box-shadow: 5px 10px 10px rgba(0,0,0,.05);
}
.account .title{
font-size: 25px;
font-weight: bold;
text-align: center;
}
.account .form-group{
margin-bottom: 20px;
}
</style>
{% block css %}{% endblock %}
</head>
<body>
<nav class="navbar navbar-default">
<div class="container">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#" style="padding-top: 0">
<a href="{% url 'index' %}"><img src="{% static 'img/head.png' %}" alt="" style="width: 100px"></a>
</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li><a href="#">产品功能</a></li>
<li><a href="#">企业方案</a></li>
<li><a href="#">帮助文档</a></li>
<li><a href="{% url 'pay' %}">价格</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
{% if request.authentication %}
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{{ request.authentication.username }} <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="{% url 'project_list' %}">管理中心</a></li>
<li role="separator" class="divider"></li>
<li><a href="{% url 'logout' %}">退 出</a></li>
</ul>
</li>
{% else %}
<li><a href="{% url 'smslogin' %}">登 录</a></li>
<li><a href="{% url 'register' %}">注 册</a></li>
{% endif %}
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
{% block content %} {% endblock %}
{% block js %}{% endblock %}
</body>
</html>
# 使用
{% extends 'bases/base.html' %}
{% block tilte %}登录{% endblock %}
{% block content %}
<div class="account">
<h1 class="text-center h1">登录</h1>
<div>
<form id="registerForm" method="post" novalidate>
{% csrf_token %}
{% for field in form %}
{% if field.name == 'code' %}
<div class="form-group">
<label for={{ field.auto_id }}>{{ field.label }}</label>
<div class="clearfix">
<div class="col-md-6" style="padding-left: 0">{{ field }}</div>
<div class="col-md-6" style="padding-right: 0"><button id="code" class="btn btn-default" style="width: 150px">点击获取验证码</button></div>
</div>
</div>
{% else %}
<div class="form-group">
<label for={{ field.auto_id }}>{{ field.label }}</label>
{{ field }}
<span style="color:red;position: absolute" class="pull-right">{{ field.errors }}</span>
</div>
{% endif %}
{% endfor %}
</form>
<button class="btn btn-primary" style="width: 150px" id="submit">登录</button>
<a href="{% url 'login' %}"> 账号密码登录 </a>
</div>
</div>
{% endblock %}
{% block js %}
<script>
let time=60;
let $codeBtn=$('#code');
let $phoneEle=$('#id_phone');
let $submitBtn=$('#submit');
let $registerForm=$('#registerForm');
$codeBtn.click(function ()
{
let $phoneNumber=$phoneEle.val();
$codeBtn.prop('disabled',true)
let remind=setInterval(function (){
$codeBtn.text(time+'秒重新发送')
time=time-1;
if (time < 1)
{
clearInterval(remind)
$codeBtn.text('点击获取验证码').prop('disabled',false)
}
},1000)
$.ajax(
{
url:'/sms/',
type:'post',
data: {
'phone': $phoneNumber, 'method':'login','csrfmiddlewaretoken':'{{csrf_token}}'
},
success: function (response) {
if(! response.code)
{
alert(response.msg.phone)
$phoneEle.val('')
}
else{
alert('验证码发送成功!')
}
}
}
)
}
)
$submitBtn.click(function (e){
$.ajax({
url:'/smslogin/',
type: 'post',
data: $registerForm.serialize(),
success:function (res){
if(res.code===1)
{
location.href=res.url
}
else
{
if (res.msg.code)
{
alert(res.msg.code)
}
else
{
alert(res.msg.phone)
}
}
}
})
})
$('input').click(function (){
$(this).next().text('').parent().removeClass('has-error')
})
</script>
{% endblock %}
#1、标签extends必须放在首行,base.html中block越多可定制性越强
#2、include仅仅只是完全引用其他模板文件,而extends却可以搭配block在引用的基础上进行扩写
#3、变量{{ block.super }} 可以重用父类的内容,然后在父类基础上增加新内容,而不是完全覆盖
#4、为了提升可读性,我们可以给标签{% endblock %} 起一个名字 。例如:
{% block content %}
...
{% endblock content %}
#5、在一个模版中不能出现重名的block标签。
inclusion_tag
- 当页面上某一块区域的内容需要在多个页面上展示的使用,并且该区域的内容需要通过传参数才能正常显示,那么可以优先考虑inclusion_tag模块。
- 定义inclusion_tag与定义过滤器和标签(inclusion_tag也是tag的一种)相同
# 需要传入一个可以渲染的模板
@register.inclusion_tag('inclusion_tags/menu_list.html')
def all_menu_list(request):
menu_list = [
{'title': '概览', 'url': reverse("dashboard", kwargs={'pk': request.project.id})},
{'title': '问题', 'url': reverse("issues", kwargs={'pk': request.project.id})},
{'title': '统计', 'url': reverse("statistics", kwargs={'pk': request.project.id})},
{'title': 'wiki', 'url': reverse("wiki", kwargs={'pk': request.project.id})},
{'title': '文件', 'url': reverse("file", kwargs={'pk': request.project.id})},
# {'title': '设置', 'url': reverse("settings", kwargs={'pk': request.project.id})}
]
for item in menu_list:
current_url = request.path_info # type:str
if current_url.startswith(item['url']):
item['style'] = "color:white"
return {'menu_list': menu_list}
# 渲染该模板
{% for item in menu_list %}
<li><a href="{{ item.url }}" {% if item.style %}style="{{ item.style }}" {% endif %}>{{ item.title }}</a></li>
{% endfor %}
# 使用
# 加载定义的inclusion_tag.py
{% load inclusion_tag %}
{% if request.project %}
<ul class="nav navbar-nav">
{% all_menu_list request=request %}
</ul>
{% endif %}
ORM模型层
ORM全称Object Relational Mapping,即对象关系映射,是在pymysq之上又进行了一层封装,对于数据的操作,我们无需再去编写原生sql,取代代之的是基于面向对象的思想去编写类、对象、调用相应的方法等,ORM会将其转换/映射成原生SQL然后交给pymysql执行。
单表操作
创建models.py
from django.db import models
class UserInfo(models.Model):
username = models.CharField(verbose_name='用户名', max_length=32, unique=True)
password = models.CharField(verbose_name='密码', max_length=32)
email = models.EmailField(verbose_name='邮箱', unique=True)
phone = models.CharField(verbose_name='手机号', unique=True, max_length=11)
# identity = models.ForeignKey(verbose_name='账户类型', to='PricePolicy', on_delete=models.DO_NOTHING)
project_num = models.IntegerField(verbose_name='创建的项目数量', default=0)
def __str__(self):
return self.username
配置数据库连接
# 可能有人考虑到mysql集群分库分表,mycat可以将mysql集群的ip+port隐藏,对外提供一个service(ip+port),所以此处只需要配一个ip+port就行
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql', # 使用mysql数据库
'NAME': 'db1', # 要连接的数据库
'USER': 'root', # 链接数据库的用于名
'PASSWORD': '', # 链接数据库的用于名
'HOST': '127.0.0.1', # mysql服务监听的ip
'PORT': 3306, # mysql服务监听的端口
'ATOMIC_REQUEST': True, #设置为True代表同一个http请求所对应的所有sql都放在一个事务中执行
#(要么所有都成功,要么所有都失败),这是全局性的配置,如果要对某个
#http请求放水(然后自定义事务),可以用non_atomic_requests修饰器
'OPTIONS': {
"init_command": "SET storage_engine=INNODB", #设置创建表的存储引擎为INNODB
}
}
}
在链接mysql数据库前,必须事先创建好数据库,下载连接
# 创建数据库
create database db1;
# 下载mysqlclient,配置了镜像就可以下载,否则会下载失败
mysqlclient==2.0.3
pip install mysqlclient
配置APP
其实按照django1.x的版本给2.x注册app也可以识别
# django1.x版本
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'app01',
]
# django2.x版本
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'app01.apps.App01Config',
]
打印sql-orm
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console':{
'level':'DEBUG',
'class':'logging.StreamHandler',
},
},
'loggers': {
'django.db.backends': {
'handlers': ['console'],
'propagate': True,
'level':'DEBUG',
},
}
}
迁移数据库
$ python manage.py makemigrations
$ python manage.py migrate
# 注意:
# 1、makemigrations只是生成一个数据库迁移记录的文件,而migrate才是将更改真正提交到数据库执行
# 2、数据库迁移记录的文件存放于app01下的migrations文件夹里
# 3、使用命令python manage.py showmigrations可以查看没有执行migrate的文件
修改字段
# 一:增加字段
#1.1、在模型类Employee里直接新增字段,强调:对于orm来说,新增的字段必须用default指定默认值
publish = models.CharField(max_length=12,default='人民出版社',null=True)
#1.2、重新执行那两条数据库迁移命令
# 二:删除字段
#2.1 直接注释掉字段
#2.2 重新执行那两条数据库迁移命令
# 三:修改字段
#2.1 将模型类中字段修改
#2.2 重新执行那两条数据库迁移命令
操作记录
新增
# 1、用模型类创建一个对象,一个对象对应数据库表中的一条记录
obj = Employee(name="Egon", gender=0, birth='1997-01-27', department="财务部", salary=100.1)
# 2、调用对象下的save方法,即可以将一条记录插入数据库
obj.save()
# 2、每个模型表下都有一个objects管理器,用于对该表中的记录进行增删改查操作,其中增加操作如下所示
obj = Employee.objects.create(name="Egon", gender=0, birth='1997-01-27', department="财务部", salary=100.1)
# 3、当form继承了forms.ModelForm时,可以调用form.save()返回一个instance对象
if request.method == 'GET':
form = RegisterForm()
return render(request, 'register.html', locals(), status=200)
elif request.method == 'POST':
res = ApiResponse()
form = RegisterForm(data=request.POST)
if form.is_valid():
instance = form.save()
查询
# 1. get(**kwargs)
# 1.1: 有参,参数为筛选条件
# 1.2: 返回值为一个符合筛选条件的记录对象(有且只有一个),如果符合筛选条件的对象超过一个或者没有都会抛出错误。
obj=Employee.objects.get(id=1)
print(obj.name,obj.birth,obj.salary)
# 2、first()
# 2.1:无参
# 2.2:返回查询出的第一个记录对象
obj=Employee.objects.first() # 在表所有记录中取第一个
print(obj.id,obj.name)
# 3、last()
# 3.1: 无参
# 3.2: 返回查询出的最后一个记录对象
obj = Employee.objects.last() # 在表所有记录中取最后一个
print(obj.id, obj.name)
# 4、count():
# 4.1:无参
# 4.2:返回包含记录对象的总数量
res = Employee.objects.count() # 统计表所有记录的个数
print(res)
# 注意:如果直接打印Employee的对象将没有任何有用的提示信息,可以在模型类中定义__str__来进行定制
class Employee(models.Model):
......
# 在原有的基础上新增代码如下
def __str__(self):
return "<%s:%s>" %(self.id,self.name)
------------------------------------------------------------------------------------
下述方法的返回值均为QuerySet类型的对象,QuerySet对象中包含了查询出的多个记录对象
# 1、filter(**kwargs):
# 1.1:有参,参数为过滤条件
# 1.2:返回值为QuerySet对象,QuerySet对象中包含了符合过滤条件的多个记录对象
queryset_res=Employee.objects.filter(department='技术部')
# print(queryset_res) # 输出: <QuerySet [<Employee: <2:Kevin>>, <Employee: <5:Jack>>, <Employee: <6:Robin>>]>
# 2、exclude(**kwargs)
# 2.1: 有参,参数为过滤条件
# 2.2: 返回值为QuerySet对象,QuerySet对象中包含了不符合过滤条件的多个记录对象
queryset_res=Employee.objects.exclude(department='技术部')
# 3、all()
# 3.1:无参
# 3.2:返回值为QuerySet对象,QuerySet对象中包含了查询出的所有记录对象
queryset_res = Employee.objects.all() # 查询出表中所有的记录对象
# 4、order_by(*field):
# 4.1:有参,参数为排序字段,可以指定多个字段,在字段1相同的情况下,可以按照字段2进行排序,以此类推,默认升序排列,在字段前加横杆代表降序排(如"-id")
# 4.2:返回值为QuerySet对象,QuerySet对象中包含了排序好的记录对象
queryset_res = Employee.objects.order_by("salary","-id") # 先按照salary字段升序排,如果salary相同则按照id字段降序排
# 5、values(*field)
# 5.1:有参,参数为字段名,可以指定多个字段
# 5.2:返回值为QuerySet对象,QuerySet对象中包含的并不是一个个的记录对象,而上多个字典,字典的key即我们传入的字段名
queryset_res = Employee.objects.values('id','name')
print(queryset_res) # 输出:<QuerySet [{'id': 1, 'name': 'Egon'}, {'id': 2, 'name': 'Kevin'}, ......]>
print(queryset_res[0]['name']) # 输出:Egon
# 6、values_list(*field):
# 6.1:有参,参数为字段名,可以指定多个字段
# 6.2:返回值为QuerySet对象,QuerySet对象中包含的并不是一个个的记录对象,而上多个小元组,字典的key即我们传入的字段名
queryset_res = Employee.objects.values_list('id','name')
print(queryset_res) # 输出:<QuerySet [(1, 'Egon'), (2, 'Kevin'),), ......]>
print(queryset_res[0][1]) # 输出:Egon
QuerySet对象:
# 过滤出符合条件的多个记录对象,然后存放到QuerySet对象中
queryset_res=Employee.objects.filter(department='技术部')
# 按照索引从QuerySet对象中取出第一个记录对象
obj=queryset_res[0]
print(obj.name,obj.birth,obj.salary)
链式处理:
# 简单示范:
res=Employee.objects.filter(gender=1).order_by('-id').values_list('id','name')
print(res) # 输出:<QuerySet [(6, 'Robin'), (5, 'Jack'), (4, 'Tom'), (2, 'Kevin')]>
其他查询API:
# 1、reverse():
# 1.1:无参
# 1.2:对排序的结果取反,返回值为QuerySet对象
queryset_res = Employee.objects.order_by("salary", "-id").reverse()
# 2、exists():
# 2.1:无参
# 2.2:返回值为布尔值,如果QuerySet包含数据,就返回True,否则返回False
res = Employee.objects.filter(id=100).exists()
print(res) # 输出:False
# 3、distinct():
# 3.1:如果使用的是Mysql数据库,那么distinct()无需传入任何参数
# 3.2:从values或values_list的返回结果中剔除重复的记录对象,返回值为QuerySet对象
res = Employee.objects.filter(name='Egon').values('name', 'salary').distinct()
print(res) # 输出:<QuerySet [{'name': 'Egon', 'salary': Decimal('100.1')}]>
res1 = Employee.objects.filter(name='Egon').values_list('name', 'salary').distinct()
print(res1) # 输出:<QuerySet [('Egon', Decimal('100.1'))]>
双下划线查询
模板:
filter(字段__关键字=值)
如:filter(id_in=[1,2,3])查询id为1,2,3的
关键字:
gt=1 大于1
lt=1 小于1
gte=1 大于等于1
let=1 小于等于1
in=[1] 在[1]内
range=[1,10] 在[1,2,3,4,5,6,7,8,9,10]内
# 模糊查询
contains="坤" 字段中有"坤"的
icontains="坤" 忽略大小写,如上
startwith="坤" 坤字开头的
endwith="坤" 坤字结尾的
month="月份" 月份
year="年份" 年份
F与Q查询
F用于比较,Q用于拼接条件
# 一张书籍表中包含字段:评论数commentNum、收藏数keepNum,要求查询:评论数大于收藏数的书籍
from django.db.models import F
Book.objects.filter(commnetNum__lt=F('keepNum'))
# 查询评论数大于收藏数2倍的书籍
from django.db.models import F
Book.objects.filter(commnetNum__lt=F('keepNum')*2)
# 修改操作也可以使用F函数,比如将每一本书的价格提高30元
ook.objects.all().update(price=F("price")+30)
# 拼接一个字符串
from django.db.models.functions import Concat
from django.db.models import Value
Employee.objects.filter(nid__lte=3).update(name=Concat(F('name'),Value('_sb')))
# 可以将条件传给类Q来实例化出一个对象,Q的对象可以使用& 和| 操作符组合起来,&等同于and,|等同于or
from django.db.models import Q
Employee.objects.filter(Q(id__gt=5) | Q(name="Egon"))
# Q 对象可以使用~ 操作符取反,相当于NOT
from django.db.models import Q
Employee.objects.filter(~Q(id__gt=5) | Q(name="Egon"))
# 当过滤条件中既有or又有and,则需要混用Q对象与关键字参数,但Q 对象必须位于所有关键字参数的前面
from django.db.models import Q
Employee.objects.filter(Q(id__gt=5) | Q(name="Egon"),salary__lt=100)
聚合
- 聚合查询aggregate()是把所有查询出的记录对象整体当做一个组。
- 分组是使用特定的条件将元数据进行划分为多个组。
- 聚合是对每个分组中的数据执行某些操作,最后将计算结果进行整合。
from django.db.models import Avg, Max, Sum, Min, Max, Count # 导入聚合函数
# 1. 调用objects下的aggregate()方法,会把表中所有记录对象整体当做一组进行聚合
res1=Employee.objects.aggregate(Avg("salary")) # select avg(salary) as salary__avg from app01_employee;
print(res1) # 输出:{'salary__avg': 70.73}
# 2、aggregate()会把QuerySet对象中包含的所有记录对象当成一组进行聚合
res2=Employee.objects.all().aggregate(Avg("salary")) # select avg(salary) as salary__avg from app01_employee;
print(res2) # 输出:{'salary__avg': 70.73}
res3=Employee.objects.filter(id__gt=3).aggregate(Avg("salary")) # select avg(salary) as salary__avg from app01_employee where id > 3;
print(res3) # 输出:{'salary__avg': 71.0}
# aggregate()的返回值为字典类型,字典的key是由”聚合字段的名称___聚合函数的名称”合成的,如`salary__avg`
# 若想定制字典的key名,我们可以指定关键参数
res1=Employee.objects.all().aggregate(avg_sal=Avg('salary')) # select avg(salary) as avg_sal from app01_employee;
print(res1) # 输出:{'avg_sal': 70.73} # 关键字参数名就会被当做字典的key
# 想得到多个聚合结果,那就需要为aggregate传入多个参数
res1=Employee.objects.all().aggregate(nums=Count('id'),avg_sal=Avg('salary'),max_sal=Max('salary'))
# 相当于SQL:select count(id) as nums,avg(salary) as avg_sal,max(salary) as max_sal from app01_employee;
print(res1) # 输出:{'nums': 10, 'avg_sal': 70.73, 'max_sal': Decimal('200.3')}
分组
分组查询annotate()相当于sql语句中的group by,是在分组后,对每个组进行单独的聚合,需要强调的是,在进行单表查询时,annotate()必须搭配values()使用:values(“分组字段”).annotate(聚合函数)
# 查询每个部门下的员工数
res=Employee.objects.values('department').annotate(num=Count('id'))
# select department,count(id) as num from app01_employee group by department;
print(res)
# 输出:<QuerySet [{'department': '财务部', 'num': 2}, {'department': '技术部', 'num': 3}, {'department': '运营部', 'num': 2}]>
跟在annotate前的values方法,是用来指定分组字段,即group by后的字段,而跟在annotate后的values方法,则是用来指定分组后要查询的字段,即select 后跟的字段
res=Employee.objects.values('department').annotate(num=Count('id')).values('num')
# select count(id) as num from app01_employee group by department;
print(res)
# 输出:<QuerySet [{'num': 2}, {'num': 3}, {'num': 2}]>
跟在annotate前的filter方法表示where条件,跟在annotate后的filter方法表示having条件
# 查询男员工数超过2人的部门名
res=Employee.objects.filter(gender=1).values('department').annotate(male_count=Count("id")).filter(male_count__gt=2).values('department')
print(res) # 输出:<QuerySet [{'department': '技术部'}]>
# 解析:
# 1、跟在annotate前的filter(gender=1) 相当于 where gender = 1,先过滤出所有男员工信息
# 2、values('department').annotate(male_count=Count("id")) 相当于group by department,对过滤出的男员工按照部门分组,然后聚合出每个部门内的男员工数赋值给字段male_count
# 3、跟在annotate后的filter(male_count__gt=2) 相当于 having male_count > 2,会过滤出男员工数超过2人的部门
# 4、最后的values('department')代表从最终的结果中只取部门名
更新
# 单条
# 1、获取记录对象
obj=Employee.objects.filter(name='Egon')[0]
# 2、修改记录对象属性的值
obj.name='EGON'
obj.gender=1
# 3、重新保存
obj.save()
----------------------------------
# 多条
queryset_obj=Employee.objects.filter(id__gt=5)
rows=queryset_obj.update(name='EGON',gender=1)
删除
# 单条
obj=Employee.objects.first()
obj.delete()
-----------------
# 多条
queryset_obj=Employee.objects.filter(id__gt=5)
rows=queryset_obj.delete()
# 全部
Employee.objects.all().delete()