一、路由控制
URL配置(URLconf)就是 Django 所支撑网站的目录。它的本质是URL与要为该URL调用的视图函数之间的映射表;客户端就是以这种方式告诉Django,对于客户端发来的某个URL调用哪一段逻辑代码对应执行
1.1、URL分组
1.1.1、简单配置
# 与django 1不同的是 django 2 3使用的是path
-第一个参数是正则表达式(如果要精准匹配:'^publish/$')
-第二个参数是视图函数(不要加括号)
-url(r'^admin/', admin.site.urls),
# django 3
from django.urls import path
from bm import views
urlpatterns = [
path("test/", views.test),
path("/test", views.hello, {"id":1})
]
def hello(request,id): pass # 从路由层就能直接将id传给hello,一个默认值
1.1.2、无名分组
- 按位置传参, 正则匹配
- 分组之后,会把分组出来的数据,当位置参数,传到视图函数,所以,视图函数需要定义形参
- django1 写法
# django 1写法
-url(r'^publish/([0-9]{4})/([0-9]{2})$', views.publish),
-def publish(request,*args): 视图函数可以这样接收
- django2\3
# django 2、3写法
from django.urls import path, re_path
urlpatterns = [
# url http://xx/port/这里可以定义最少0个-4个的数字
# re_path('^port/([0-9]{0,4})$', views.port),
# 匹配数字的url
re_path("^test2/(\d+)$", views.port),
# 匹配字母数字及下划线,啥样的格式皆可
re_path("^test3/(\w+)$", views.test3)
]
# 后端的port可以接收到 url中传递的无名分组
def port(request, num):
print(num)
return HttpResponse("port")
1.1.3、有名分组
- 按关键字传参
- 有名分组之后,会把分组出来的数据,当关键字参数,传到视图函数,所以,视图函数需要定义形参,形参名字要跟分组的名字对应,与顺序无关
- 有名分组,函数接收需要 关键字参数, 语法: (?P 这里写正则 )
- django1
-url(r'^publish/(?P<year>[0-9]{4})/(?P<mounth>[0-9]{2})/$', views.publish),
-def publish(request, mounth,year):
*****有名分组和无名分组,不要混用
-
django2\3
from django.urls import path, re_path from test_url import views urlpatterns = [ re_path("^test4/(?P<one>\d+)", views.test4), ] # 浏览器中Url: /test4/1111 def test4(request, **kwargs): print(kwargs) # {'one': '1111'} return HttpResponse("test4")
1.1.4、PATH 转化器
Django默认支持以下5个转化器: 语法:
path('test/<int:name>', test)
- str,匹配除了路径分隔符(
/
)之外的非空字符串,这是默认的形式 - int,匹配正整数,包含0。
- slug,匹配字母、数字以及横杠、下划线组成的字符串。
- uuid,匹配格式化的uuid,如 075194d3-6885-417e-a8a8-6c931e272f00。
- path,匹配任何非空字符串,包含了路径分隔符(/)(不能用?)
urlpatterns = [
# path("test5/<int:key1>", views.test5),
path("test5/<path:key1>", views.test5), # 如 {'key1': '321/adf1/vheid'}
# path("test5/<str:key1>", views.test5),
]
1.1.5、append_flash
# settings.py中添加这个,就不会自动在url后添加 / 了
APPEND_FLASH = False
1.2、反向解析(url,reverse)
如果在html页面中直接死写url,那么后期在修改函数名时会造成代码修改过多,过于复杂的局面,此时使用反向解析就很有必要了
1.2.1、无参数
# 从html页面 test6 中点击跳转, 逐步跳到 test8 反向解析
# 1、 m:url
urlpatterns = [
re_path("test6/$", views.test6, name="test6"),
re_path("test7/$", views.test7, name="test7"),
re_path("test8/$", views.test8, name="test8"),
]
# 2、 v:视图
from django.shortcuts import render, HttpResponse, reverse, redirect
def test6(request):
return render(request, "test6.html")
def test7(request):
# 也可以直接在这里打印
return redirect(reverse("test8"))
def test8(request):
return HttpResponse("test7 在跳 test8")
# 3、 t:模板
<a href='{% url "test7" %}'>点我跳转</a>
1.2.2、无名分组
# 1、 m:url
urlpatterns = [
re_path("test6/", views.test6, name="test6"),
re_path("test7/(\d+)", views.test7, name="test7"),
re_path("test8/(\d+)/$", views.test8, name="test8"),
]
# 2、 v:视图, 要是有多个参数,也是一样传
from django.shortcuts import render, HttpResponse, reverse, redirect
def test6(request):
return render(request, "test6.html")
def test7(request, *args):
# 注意 args=(参数,) 或 args=(参数1,参数2,.....) # 注意要有逗号
return redirect(reverse("test8", args=(args[0],)))
def test8(request, *args):
return HttpResponse("test7 在跳 test8")
# 3、 t:模板
<a href='{% url "test7" 2022 %}'>点我跳转</a>
1.2.3、有名分组
# 1、 m:url
urlpatterns = [
re_path("test6/", views.test6, name="test6"),
re_path("test7/(?P<arg1>\d+)/(?P<arg2>\d+)", views.test7, name="test7"),
re_path("test8/(\d+)/(\d+)/$", views.test8, name="test8"),
re_path("test9/(?P<arg1>\d+)/(?P<arg2>\d+)", views.test9, name="test9"),
]
# 2、 v:视图
def test6(request):
return render(request, "test6.html")
def test7(request, **kwargs):
# print(kwargs) # {'arg1': '2022', 'arg2': '2023'}
# return redirect(reverse("test8", args=(1111,222,)))
return redirect(reverse("test9", kwargs=kwargs))
# 注意 test8跟test9的区别, 位置参数跟关键字参数
def test8(request, *args):
return HttpResponse("test7 在跳 test8")
# 返回values至前端
def test9(request, **kwargs):
return HttpResponse(kwargs.values())
# 3、 t:模板
<a href='{% url "test7" arg1=2022 arg2=2023 %}'>点我跳转</a>
# 也可以 <a href='{% url "test7" 2022 2023 %}'>点我跳转</a>
总结
# URL
from django.urls import path, re_path
无参数: re_path("test6/", views.test6, name="test6"),
有名分组: re_path("test7/(?P<arg1>\d+)/(?P<arg2>\d+)", views.test7, name="test7"),
无名分组: re_path("test8/(\d+)/(\d+)/$", views.test8, name="test8"),
# 视图
导入模块: from django.shortcuts import reverse
return redirect(reverse("test8", args=(1111,222,)))
return redirect(reverse("test9", kwargs={”key1":"value1",”key2":"value2",}))
# 模板层:
无参数: {% url 'test6' %}
无名分组的:{% url 'test7' 2021 2022 %}
有名分组: {% url 'test7' 2021 2022 %} 还可以 {% url 'ddd' arg1=2021 arg2=2022 %}
1.3、路由分发-include
from django.urls import path, re_path, include
# 将多个app在根url下统一进行分发
urlpatterns = [
re_path('^app01/', include('app01.urls')),
re_path('^app02/', include('app02.urls')),
]
# app01应用下创建urls.py文件
from django.urls import path, re_path, include
from test_url import views
urlpatterns = [
re_path("test6/", views.test6, name="test6"),
]
# 在不同的app里创建urls.py
# 在不同的app的urls里配置路由关系
# ***重点***总路由,不能加结束符$
1.4、名称空间
namespace, django2 后已删除, 官方说明
Support for setting a URL instance namespace without an application namespace is removed.
-url(r'^blog/',include('blog.urls',namespace='blog')),
-子路由:url(r'^publish/$', views.publish,name='test'),
-反向解析:
-视图层:url = reverse('blog:test')
-模板层:{% url 'app01:test'%}
***一般不要用***
子路由:url(r'^publish/$', views.publish,name='app01_test'),
django2 后使用就变成了 app_name
from django.urls import path, re_path, include
# 将多个app在根url下统一进行分发
urlpatterns = [
path('app01/', include('app01.urls')),
]
# 当有多个相同的名称时, 就可以使用 app_name, 在app01.urls中定义
app_name="app01"
urlpatterns = [
path('test/', views.test, name="test"),
]
在视图中定直接就可以使用 return redirect(reverse("app01:test")), 跟名称空间一样
匹配demo
# 直接匹配根目录 re_path("^$", views.index),
# 匹配不到的页面返回404 re_path("", views.errors)
二、视图层
views.py, 通过include可以对应不同的视图层
2.1、视图层对象
2.1.1、HttpRequest
# 前台Post传过来的数据,包装到POST字典中 request.POST
# 前台浏览器窗口里携带的数据,包装到GET字典中 request.GET
# 前台请求的方式 request.method
# post提交的数据,body体的内容,前台会封装成:name=lqz&age=18&sex=1 request.body
# 取出请求的路径,取不到数据部分 print(request.path)
# 取出请求的路径,能取到数据部分
# print(request.get_full_path()) \ print(request.META)
2.1.2、JsonResponse
如果想要前台端分离,那么就可以直接使用JsonReponse来直接传递
form django.http import JsonResponse
def jsontest(request):
# JsonResponse 实际部署
# 字典形式返回
# import json
# dic = {"id": 1, "teacher": "xiong"}
# return HttpResponse(json.dumps(dic))
# dic = {"id": 1, "teacher": "xiong"}
# return JsonResponse(dic)
# 列表
# import json
jlist = ['xiong', 'wen']
# return HttpResponse(json.dumps(jlist))
# return JsonResponse(jlist) # 默认jsonresponse不支持直接转化列表
return JsonResponse(jlist, safe=False) # 可以设置safe为false就能直接在前台展示
2.2、CBV、FBV
2.2.1、CBV
# CBV: 基于类的视图
urlpatterns = [
path("fbvtest/", Vtest.as_view()),
]
class Vtest(View):
def get(self, request):
return HttpResponse("get")
def post(self, request):
return HttpResponse("post")
# 源码分析 Vtest.as_view()
1、到as_view
@classonlymethod # 类方法
def as_view(cls, **initkwargs):
def view(request, *args, **kwargs): # 闭包函数
self = cls(**initkwargs)
self.setup(request, *args, **kwargs)
。。。
return self.dispatch(request, *args, **kwargs) # 分发
2、分发到 dispatch
# return self.dispatch(request, *args, **kwargs)
def dispatch(self, request, *args, **kwargs):
# Try to dispatch to the right method; if a method doesn't exist,
# defer to the error handler. Also defer to the error handler if the
# request method isn't on the approved list.
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs)
3、类的方法
# request.method.lower() in self.http_method_names
http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head',。。]
# 如果有这样方法 则直接返回 return handler(request, *args, **kwargs)
handler就相当于是 get(request, *args, **kwargs)
4、返回handler, self.http_method_names 对应的方法
Vtest.as_view() = return self.dispatch = return handler
最终就相当是 Vtest.http_method_names(request, *args, **kwargs))
2.2.2、FBV
# FBV: 基于函数的视图
re_path('list/(?P<name>(\w)+)$', applist, name="applist"),
def applist(request):
if request.method == "GET":
return redirect(reverse('applist', kwargs={"name": "xiong"}))
if request.method == "POST":
return HttpResponse("hello post")
2.3、文件上传
- CBV
# 视图层
class Upfile(View):
def get(self, request):
return render(request, "files/upfiles.html")
def post(self, request):
# type(request.FILES)
# from django.utils.datastructures import MultiValueDict
print(type(request.FILES.get("get_file")))
# django.core.files.uploadedfile.InMemoryUploadedFile
from django.core.files.uploadedfile import InMemoryUploadedFile
get_file = request.FILES.get("get_file")
print(get_file.content_type.rsplit("/")[1]) # 获取类型
abs_file = os.path.join(BASE_DIR, "media", get_file.name)
with open(abs_file, "wb") as file:
for line in get_file.chunks():
file.write(line)
return HttpResponse("ok")
# URL层
urlpatterns = [
path("upfile/", views.Upfile.as_view(), name="file_upfile")
]
# 模板
<form action="{% url "file_upfile" %}" method="post" enctype="multipart/form-data">
<p>上传文件:<input type="file" name="get_file"></p>
<p><input type="submit" value="提交"></p>
</form>
- FBV
# views.py
def fileupload(request):
if request.method == "GET":
return render(request, 'fileup.html')
if request.method == "POST":
upfile = request.FILES.get('upfile')
# from django.core.files.uploadedfile import InMemoryUploadedFile
savefile = os.path.dirname(os.path.dirname((os.path.abspath(__file__))))
savefiledir = os.path.join(savefile, 'media', upfile.name)
with open(savefiledir, 'wb') as file:
for line in upfile.chunks():
file.write(line)
return HttpResponse("ok")
# urls层
urlpatterns = [
path('admin/', admin.site.urls),
path('fileupload/', views.fileupload),
]
template html
<form action="/fileupload/" method="post" enctype="multipart/form-data">
<div><input type="file" name="upfile"></div>
<div><input type="submit" value="提交"></div>
</form>
2.3.1、源码分析
# 需要格外注意的是 这里的格式一定要是 enctype="multipart/form-data">
# python 只处理了 application/x-www-form-urlencoded,multipart/form-data这两种格式上传文件
print(type(request.FILES.get("get_file")))
# from django.core.files.uploadedfile import InMemoryUploadedFile
class InMemoryUploadedFile(UploadedFile):
"""
A file uploaded into memory (i.e. stream-to-memory).
"""
def __init__(self, file, field_name, name, content_type, size, charset, content_type_extra=None):
super().__init__(file, name, content_type, size, charset, content_type_extra)
self.field_name = field_name
def open(self, mode=None):
self.file.seek(0)
return self
def chunks(self, chunk_size=None):
self.file.seek(0)
yield self.read()
三、模板层
-
对页面设计进行的任何改变都必须对 Python 代码进行相应的修改。 站点设计的修改往往比底层 Python 代码的修改要频繁得多,因此如果可以在不进行 Python 代码修改的情况下变更设计,那将会方便得多。
-
Python 代码编写和 HTML 设计是两项不同的工作,大多数专业的网站开发环境都将他们分配给不同的人员(甚至不同部门)来完成。 设计者和HTML/CSS的编码人员不应该被要求去编辑Python的代码来完成他们的工作。
-
程序员编写 Python代码和设计人员制作模板两项工作同时进行的效率是最高的,远胜于让一个人等待另一个人完成对某个既包含 Python又包含 HTML 的文件的编辑工作。
-
基于这些原因,我们可以将页面的设计和Python的代码分离开会更干净简洁更容易维护。 我们可以使用 Django的 *模板系统* (Template System)来实现这种模式
3.1、变量&过滤器&标签
3.1.1、变量
# 通过 locals() 可以将 函数内的所有属性都传到前端
class Temptest(View):
def get(self, request):
strs = "xiong"
tu = (1,2,3,4)
dic = {"name": "xiong", "ll": ["tom", "col", "local"]}
lis = ["tom", "ren", "requ"]
def test():
return "hello"
class Tests:
def __init__(self, name, age):
self.name = name
self.age = age
def get_age(self):
return self.age
@classmethod
def get_tests(cls):
return "我是类方法"
t = Tests("xiong", 100)
gtime = datetime.datetime.now()
return render(request, "files/temps.html", locals())
# 模板变量 {# 模板层的注释,在前端页面中不会被查询访问到 #}
<p>字符串: {{ strs }}</p>
<p>元组: {{ tu }}</p>
<p>字典加列表: {{ dic }}</p> # 字典加列表: {'name': 'xiong', 'll': ['tom', 'col']}
<p>列表: {{ lis }}</p> # 列表: ['tom', 'ren', 'requ']
<p>函数: {{ test }}</p> {# 相当于是 直接 print(test()) #}
<p>类: {{ t }}</p>
{# <files.views.Temptest.get.<locals>.Tests object at 0x0。。> #}
<hr>
<p>从字典中取列表: {{ dic.ll }}</p> {# 通过句点符来取出 #}
<p>从类取方法: {{ t.get_age }}</p> {# 不需要加括号就可以直接执行 #}
<p>从类中取类方法: {{ t.get_tests }}</p>
3.1.2、模板-过滤器
# 语法: <第一个参数:过滤器属性:第二个参数>
<p>获取长度: {{ strs|length }}</p> # 获取长度: 5
<p>列表默认值: {{ lis|default:"none" }}</p> # 列表默认值: ['tom', 'ren', 'requ']
<p>filesizeformat: {{ 122222|filesizeformat }}</p> # 转换大小文件: 119.4 KB
# 从第3个字符串开始算truncatechars: hello w…
<p>truncatechars: {{ "hello world world"|truncatechars:8 }}</p>
<p>get_time: {{ gtime|date }}</p> # get_time: Jan. 11, 2021
<p>get_time: {{ gtime|date:"Y-m-d" }}</p> # get_time: 2021-01-11
{# 后端传到前端的页面需要加上safe但与之相对的会有css攻击风险 #}
<p>{{ page|safe}} </p> # 如果无法判断页面是否安全, 则不要使用
3.1.3、过滤器-扩展
3.1.4、标签
模版语法:标签 {% %}, for, if, with 都需要有结束,前面加一个end
-
for
{% for foo in lab_dic %} {# {{ forloop }}#} 前台打印结果: {# {'parentloop': {}, 'counter0': 0, 'counter': 1, 'revcounter': 2, 'revcounter0': 1, 'first': True, 'last': False}#} {# revcounter 倒序查看从1开始 counter 从1开始 后面加个零表示从0开始 #} <p>{{ forloop.revcounter }} --> {{ forloop.counter }} --> {{ foo.name }}</p> {% endfor %} {# 测试empty 但字典或列表为空才执行 #} {% for foo in lab_null_for %} <p>{{ foo }}</p> {% empty %} <p>空值</p> {% endfor %}
-
if
{# 判断是否非空, 如果是空的打印if 如果不是打印lab_null_for #} {% if not lab_null_for %} <p>空的</p> {% else %} <p>{{ lab_null_for }}</p> {% endif %}
-
with
{# 打印别名, 在with如果有多行一样的代码,定义成别名可简化代码量 #} {% with lab_dic.1.name as dic %} <p>打印with别名, 直接获取列表中字典第二个的name: {{ dic }}</p> {% endwith %} # 需要包含在with中才能被打印
3.2、模板-自定义
过滤器、标签前几步的步骤都一样, 需要注意的是: 标签不能用在if判断,过滤器可以用在if判断
- 先在setting.py下注册app
- 在app下创建 templatetags文件夹,如
test/templatetags/my_filter.py
- 新建一个py文件 如 my_filter.py
3.2.1、自定义过滤器-filter
-
仿length过滤器
# 自定义一个仿length的过滤器 from django.template import Library register = Library() @register.filter(name="clength") def custom_length(value): if value is not None: if isinstance(value,int): value = str(value) return len(str(value)) raise TypeError("类型错误或为空")
-
仿add过滤器,支持数字、字符串
@register.filter() def custom_add(value1, value2): try: return int(value1) + int(value2) except ValueError: return str(value1) + str(value2) except: return "类型或其它错误"
-
html中调用
# html中使用 # 一、先load {% load my_filter %} # 需要注意的是这里应使用模板的方法导入 # 二、使用 {{ name|clength }} # name是从views.py中传入 {{ name|custom_add:123 }} # 如果长度name是整形或为空则判断错误,其它类型基本能判断出来 # 添加name,如果是列表或字典\set类型,则会返回类型错误, 类型错误会先内部转换
-
views.py
def custest(request): # name="xiong" name = 123 return render(request, "custom/custom_index.html", locals())
3.2.2、自定义标签-simple_tag
-
拼接3个参数
@register.simple_tag() def custom_simple(value1, value2, value3): return value1 + value2 + value3 # 模板中引用, 页面中的结果就是 xiong+++nnnnn {% custom_simple "xiong" "+++" "nnnnn" %}
-
返回html
from django.template import Library from django.utils.safestring import mark_safe register = Library() @register.simple_tag def my_input(id, arg): result = "<input type='text' id='%s' class='%s' />" %(id, arg,) return mark_safe(result) # 界面效果就是一个Input框 # return result # <input type='text' id='123' class='321' />
3.2.3、html代码片段
inclusion_tag 与simple_tag有点类型但不太一样,这个是直接返回html页面
-
创建标签属性
from django.template import Library register = Library() # def inclusion_tag(self, filename, func=None, takes_context=None, name=None): # 只传一个filename @register.inclusion_tag("test.html") def my_inclusion(num): # 这里应该做一个判断 li = ["hello {}".format(i) for i in range(int(num))] return {"li": li} {"li":[1,2,3,4,5]}
-
创建test.html, 注意这个文件应该放在 template目录下
<ul> {% for foo in li %} <li>{{ foo }}</li> {% endfor %} </ul>
-
引用
{% load my_html %} {% my_inclusion 10 %} # 10行
-
效果
hello 0 hello 1 hello 2
3.3、模板-导入&继承
3.3.1、母板及继承关系
- 视图&url
# url访问
urlpatterns = [
path("temp/", views.temp)
]
# 视图
def temp(request):
return render(request, "custom/temp_test.html")
- 模板母板
base.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="/static/bootstrap.css">
<style>
.header {
height: 100px;
background-color: #c6cef6;
}
</style>
</head>
<body>
<div class="header"></div>
<div class="container-fluid">
<div class="row">
<div class="col-md-2">
{% include 'custom/left.html' %} {# 引入模板、继承left.html #}
</div>
<div class="col-md-9">
{% block context %} # 子模版可能会覆盖掉模版中的这些位置
<p>hello world</p> # 母板中的内容,在子模板引用会被覆盖
{% endblock %}
</div>
</div>
</div>
</body>
</html>
- 模板导入
custom/left.html
在母板中的这一句:{% include 'custom/left.html' %},导入的内容
<div class="card" style="width: 18rem;">
<p></p>
<ul class="list-group list-group-flush">
<li class="list-group-item">Cras justo odio</li>
<li class="list-group-item">Dapibus ac facilisis in</li>
<li class="list-group-item">Vestibulum at eros</li>
</ul>
</div>
- url中定义的
custom/temp_test.html
{% extends 'custom/base.html' %} # 继承母板
{% block context %} # 母板中传递的盒子数据,可以在这里定义
<p>1111111111111</p>
<p>1111111111111</p>
{{ block.super }} # 母板中的某些内容,可以被继承过来使用
{% endblock context %} # 如果有多个盒子可以直接使用 block
3.3.2、模板-静态文件
-
静态文件相关的配置
# 定义别名, 这个只是STATICFILES_DIRS定义之后路径的别名 如/static/css/ 而实际可能是/hello/static/css STATIC_URL = '/static/' # 实际的静态文件路径, 以逗号分割可以有多个路径 STATICFILES_DIRS = [ os.path.join(BASE_DIR, 'static') ]
-
html页面中的静态引入方式
# 三种 # 当定义的别名更改时,这个路径也得更改 直接引用: <link rel="stylesheet" href="/static/css/my.css"> 标签引用一 毕竟得先引用标签, 然后在模版中引用,这些当别名更改后,路径也会随static_url更改 {% load static %} <link rel="stylesheet" href="{% static 'css/my.css' %}"> 标签引用二 意思同上 {% load static %} <link rel="stylesheet" href="{% get_static_prefix %}css/my.css">