- Django基础三之路由、视图、模板
- 1. Django 请求和返回周期
- 1.1 路由层之路由匹配
- 1.2 有名分组
- 1.3 无名分组
- 2. 反射解析
- 3. 路由分发
- 4 名称空间
- 5. JsonResponse
- 6. 上传文件
- 7. FBV和CBV
- 8. 模板语法传值
- 8.1 传基本数据类型
- 8.2 传函数名
- 8.3 传类名
- 9. 模板语法获取值
- 10. 模板语法过滤器
- 11. 模板语法标签(流程控制)
- 12. 自定义过滤器、标签、inclusion_tag
- 12.1 自定义过滤器:
- 12.2 自定义标签
- 12.3 自定义inclusion_tag
- 13. 模板的导入
- 14. 模板的继承
- 1. Django 请求和返回周期
Django
默认使用wsgiref
模块但是该模块并发量特别小(大约1000),不适用于线上环境,所以在Django
项目上线之后会使用uwsgi
。
主要是在ursl.py
文件里书写。
1.11版本:
urlpatterns = [
url('^admin/', admin.site.urls),
]
3.2版本:
urlpatterns = [
path('admin/', admin.site.urls),
path('test/', views.test),
path('testadd/', views.testadd),
]
1版本中使用url方法:
url()方法:
1,第一个参数为一个正则
2,只要能匹配上就会执行后面的视图函数
3版本中使用path
path()方法
第一个参数是一个字符串
如果使用正则,则要使用 re_path() 而不是 path() 。
urlpatterns = [
re_path(r'^admin/', admin.site.urls),
]
test/和testadd/ 在匹配的时候如果不写后面的斜杠(/),发现也能匹配上,是因为Django在做的时候如果test匹配不上,它会让浏览器后面自动加上斜杠(/)再试一次。
这个是用settings里面的APPEND_SLASH参数控制,默认为True,如果只想匹配一次则设置为False.
APPEND_SLASH=False
在Django3.x
在匹配时有了路径转换器:
1.2 有名分组
str
- 匹配除了'/'
之外的非空字符串。如果表达式内不包含转换器,则会默认匹配字符串。
int
- 匹配 0 或任何正整数。返回一个int
。path('articles/<int:year>/', views.year_archive), <int:year>是个整型参数
slug
- 匹配任意由 ASCII 字母或数字以及连字符和下划线组成的短标签。比如,building-your-1st-django-site
。
uuid
- 匹配一个格式化的 UUID 。为了防止多个 URL 映射到同一个页面,必须包含破折号并且字符都为小写。比如,075194d3-6885-417e-a8a8-6c931e272f00
。返回一个UUID
实例。
path
- 匹配非空字段,包括路径分隔符'/'
。它允许你匹配完整的 URL 路径而不是像str
那样匹配 URL 的一部分。
命名正则表达式组的语法是 (?P<name>pattern)
其中 name
是组名,pattern
是要匹配的模式
在Django3中路由匹配使用正则:
ursl.py文件:
from django.contrib import admin
from django.urls import path,re_path #要手动导入re_path
from orm import views
urlpatterns = [
path('admin/', admin.site.urls),
path('test/', views.test),
path('testadd/', views.testadd),
re_path(r'test/(?P<year>[0-9]{4})/', views.testadd),
]
在views.py:
def testadd(request,year):
print(year)
return HttpResponse("from test")
// 分组名必须要传给后面的视图函数,否则会报错。
如上面的例子,分组名为year,如果不传给后端的views.testadd函数,报错信息:
testadd() got an unexpected keyword argument 'year'
有名分组
将括号内正则表达式匹配到的内容当做关键字参数传递给后面的视图函数
1.3 无名分组
有命名组语法,例如 (?P<year>[0-9]{4})
,你也可以使用更短的未命名组,例如 ([0-9]{4})
。
在Django3中路由匹配使用正则:
ursl.py文件:
from django.contrib import admin
from django.urls import path,re_path #要手动导入re_path
from orm import views
urlpatterns = [
path('admin/', admin.site.urls),
path('test/', views.test),
path('testadd/', views.testadd),
re_path(r'test/([0-9]{4})/$', views.test),
]
启动访问:
http://127.0.0.1:8000/test/1234/
报错:
test() takes 1 positional argument but 2 were given
解决方法:
在views.py:
def test(request,what):
print(what)
return HttpResponse("from test")
再执行访问成功。
控制台打印的结果:
1234
无名分组:
将括号内正则表达式匹配到的内容当做位置参数传递给后面的视图函数。
总结:
- 有名分组和无名分组不能混合使用。
- 单个种类可以重复使用
当路由频繁变化的时候,HTML
界面上的连接地址如何做到动态解析。
"""
1. 给路由与视图函数对应关系添加一个别名(名字自己定义,名字之间不要冲突)
path('show/', views.show, name='showtime'),
2. 根据这个别名动态解析出一个结果,该结果可以直接访问到对应的路由
前端使用别名:
<a href="{% url 'showtime' %}"><h1>Hello Django</h1></a>
这样不管path里面的show怎么变,只要name='showtime'不变,那么访问就没问题
后端使用别名:
ursl.py:
urlpatterns = [
path('show/', views.show, name='showtime'),
]
views.py
from django.shortcuts import render, HttpResponse,redirect,reverse
def delete(request):
......
print(reverse('showtime')) # 打印url
return redirect('showtime') # 也可以直接在重定向里写别名
"""
无名和有名分组指向解析
ursl.py
"""
from django.urls import path,re_path
urlpatterns = [
re_path(r'test/([0-9]{4})/$', views.test, name='index_test'),
]
"""
views.py
"""
def delete(request):
......
print(reverse('index_test',args=(1,))) # 打印url
args=(1,) args后面跟一个元组,里面这写的是1,推荐写主键的值
r'test/([0-9]{4})/([0-9]{4})/$ 如果有两个分组,则args后面必须写两个值,(1,2)第二个值可以随便写
"""
前端也一样:
<a href="{% url 'index_test' 123 %}"><h1>Hello Django</h1></a>
这里123也是随便写的,只要写个数字就行
有名:
后端
reverse('index_test',kwargs={'id':123})
前端
<a href="{% url 'index_test' id=123 %}"><h1>Hello Django</h1></a>
总结
无名和有名都可以使用一种(无名)反向解析的形式
3. 路由分发其实Django
中的每一个应用都可以有自己的urls.py
、static
文件夹、templates
文件夹,这样使用Django
做分组开发非常的简便。每个人只需要写息的应用即可,最后汇总到一个空的Django
项目中然后使用路由分发将多个应用关联。
示例:
创建一个项目,并创建两个应用(app01,app02).
在每个应用里面都创建一个urls.py文件。
app01 urls.py:
"""
from django.urls import path
from app01 import views
urlpatterns = [
path('index', views.index),
]
"""
app01 views.py:
"""
from django.shortcuts import render,HttpResponse
# Create your views here.
def index(request):
return HttpResponse("from app01 index")
"""
app02 urls.py:
"""
from django.urls import path
from app02 import views
urlpatterns = [
path('index', views.index),
]
"""
app02 views.py:
"""
from django.shortcuts import render,HttpResponse
# Create your views here.
def index(request):
return HttpResponse("from app02 index")
"""
项目中总的urls.py:
"""
from django.contrib import admin
from django.urls import path,include
# 导入应用的urls
from app01 import urls as app01_urls
from app02 import urls as app02_urls
urlpatterns = [
path('admin/', admin.site.urls),
path('app01/', include(app01_urls)),
path('app02/', include(app02_urls)),
]
"""
注意:
需要在总的urls.py里导入include
from django.urls import path,include
在总的路由里面不能加$符号,否则没办法分发
还有一种在总的urls.py里不需要导入应用urlsr 的方法:
项目中总的urls.py:
"""
from django.contrib import admin
from django.urls import path,include
urlpatterns = [
path('admin/', admin.site.urls),
path('app01/', include('app01.urls')),
path('app02/', include('app02.urls')),
]
"""
4 名称空间
当多个应用在反射解析的时候如果出现了别名冲突的情况,那么将会无法自动识别
示例:
app01 urls.py:
"""
from django.urls import path
from app01 import views
urlpatterns = [
path('index', views.index,name='index_name'),
path('login', views.login)
]
"""
app01 views.py:
"""
from django.shortcuts import render,HttpResponse,reverse
# Create your views here.
def index(request):
return HttpResponse("from app01 index")
def login(request):
print(reverse('index_name'))
return HttpResponse("from app01 login")
"""
app02 urls.py:
"""
from django.urls import path
from app02 import views
urlpatterns = [
path('index', views.index,name='index_name'),
path('login', views.login),
]
"""
app02 views.py:
"""
from django.shortcuts import render,HttpResponse,reverse
# Create your views here.
def index(request):
return HttpResponse("from app02 index")
def login(request):
print(reverse('index_name'))
return HttpResponse("from app02 login")
"""
项目中总的urls.py:
"""
from django.contrib import admin
from django.urls import path,include
urlpatterns = [
path('admin/', admin.site.urls),
path('app01/', include('app01.urls')),
path('app02/', include('app02.urls')),
]
"""
虽然访问页面:
http://127.0.0.1:8000/app01/login
http://127.0.0.1:8000/app02/login
的时候能正常拿到对应的页面,但是在后端发现拿到的是同一个:
/app02/index
/app02/index
要解决这个问题就用到了名称空间
解决方法一:使用名称空间
在总路上加上namespace这个参数:
项目中总的urls.py:
"""
from django.contrib import admin
from django.urls import path,include
urlpatterns = [
path('admin/', admin.site.urls),
path('app01/', include('app01.urls',namespace='app01')),
path('app02/', include('app02.urls',namespace='app02')),
]
"""
app01 urls.py:
"""
from django.urls import path
from app01 import views
app_name='app01'
urlpatterns = [
path('index', views.index,name='index_name'),
path('login', views.login)
]
"""
app01 views.py:
"""
from django.shortcuts import render,HttpResponse,reverse
# Create your views here.
def index(request):
return HttpResponse("from app01 index")
def login(request):
print(reverse('app01:index_name'))
return HttpResponse("from app01 login")
"""
app02 urls.py:
"""
from django.urls import path
from app02 import views
app_name='app02'
urlpatterns = [
path('index', views.index,name='index_name'),
path('login', views.login),
]
"""
app02 views.py:
"""
from django.shortcuts import render,HttpResponse,reverse
# Create your views here.
def index(request):
return HttpResponse("from app02 index")
def login(request):
print(reverse('app02:index_name'))
return HttpResponse("from app02 login")
"""
访问页面:
http://127.0.0.1:8000/app01/login
http://127.0.0.1:8000/app02/login
拿到的就是
/app01/index
/app02/index
注意在Django3.2版本中使用名称空间的时候,一定要给每个应用设置应用名,否则会报错:
'''pecifying a namespace in include() without providing an app_name is not supported. Set the app_name attribute in the included module, or pass a 2-tuple containing the list of patterns and app_name instead.'''
解决方法:
app01 urls.py:
'''
app_name='app01'
'''
app02 urls.py:
'''app_name='app02''''
这两个必须要设置。
前端使用名称空间:
<a href="{% url 'app01:index_name' %}">app01_index</a>
<a href="{% url 'app02:index_name' %}">app02_index</a>
注意:
虽然我们现在可以将模板文件直接放在
app01/templates
文件夹中(而不是再建立一个app01
子文件夹),但是这样做不太好。Django 将会选择第一个匹配的模板文件,如果你有一个模板文件正好和另一个应用中的某个模板文件重名,Django 没有办法 区分 它们。我们需要帮助 Django 选择正确的模板,最好的方法就是把他们放入各自的 命名空间 中,也就是把这些模板放入一个和 自身 应用重名的子文件夹里。(app01/templates/app01/login.html)同理:多个应用下的静态文件也是这样。
所以在前端使用名称空间的时候,HTML
文件的路径为:
app01/templates/app01/login.html
app02/templates/app02/login.html
后端app01 views.py:
from django.shortcuts import render,HttpResponse,reverse
def login(request):
print(reverse('app01:index_name'))
return render(request, "app01/login.html")
后端app02 views.py:
from django.shortcuts import render,HttpResponse,reverse
def login(request):
print(reverse('app02:index_name'))
return render(request, "app02/login.html")
解决方法二:别名别冲突
写别名的时候要加上自己应用名做前缀。
5. JsonResponse给前端返回一个json
格式的数据
方法一:自己序列化
views.py:
from django.shortcuts import render,HttpResponse,reverse
import json
def index(request):
d = {'user':'Hans', 'password':123}
d_json = json.dumps(d)
return HttpResponse(d_json)
# json默认不能直接识别的字符直接返回对应的unicode编码,如上面的汉字要正确返回则需要设置ensure_ascii=False
d = {'user':'Hans你好', 'password':123}
d_json = json.dumps(d,ensure_ascii=False)
方法二: 使用JsonResponse
views.py:
from django.shortcuts import render,HttpResponse,reverse
from django.http import JsonResponse
def index(request):
d = {'user':'Hans', 'password':123}
return JsonResponse(d)
# JsonResponse 对不能识别的字符也是直接返回unicode编码,如果对汉字也能正常展示,加上json_dumps_params={'ensure_ascii':False}参数:
d = {'user':'Hans你好', 'password':123}
return JsonResponse(d,json_dumps_params={'ensure_ascii':False})
# 如果序列化一个非字典类型的,则需要让safe=False
如:
li = ['A','B','C']
return JsonResponse(d, safe=Fasle)
6. 上传文件
前端页面:
<form action="" method="post" enctype="multipart/form-data" class="form-control">
<p><input type="file" name="files"></p>
<p><input type="submit" value="提交" ></p>
</form>
# 路由层:
path('upfile', views.upfile),
# 视图层
views.py:
from django.shortcuts import render,HttpResponse,reverse
def upfile(request):
if request.method == 'POST':
file_obj = request.FILES.get('files')
print(file_obj.name)
with open(r'./app01/templates/%s' % file_obj.name, 'wb') as f:
for chunk in file_obj.chunks():
f.write(chunk)
return render(request,"app01/upfile.html")
7. FBV和CBV
FBV
:基于函数的视图
CBV
:基于类的视图
上面写的都为FBV
,基于函数的视图,现在写一个基于类的视图。
# views.py
from django.shortcuts import render,HttpResponse,reverse
from django.views import View
class MyView(View):
def get(self,request):
return HttpResponse("GET方法")
def pos(self,request):
return HttpResponse("POST方法")
# urls.py
from django.urls import path
from . import views
urlpatterns = [
path('myview', views.MyView.as_view()),
]
#CBV和FBV路由匹配其实是一样的。
8. 模板语法传值
8.1 传基本数据类型
# 方法一:精确传值
# urls.py
"""
from django.contrib import admin
from django.urls import path
from templateByValue import views
urlpatterns = [
path('admin/', admin.site.urls),
path('index/', views.index),
]
"""
# 前端HTML:
"""
<body>
<ul>{{ i }}</ul>
<ul>{{ f }}</ul>
<ul>{{ str }}</ul>
<ul>{{ Li }}</ul>
<ul>{{ set01 }}</ul>
<ul>{{ t }}</ul>
<ul>{{ bool_value }}</ul>
<ul>{{ d }}</ul>
</body>
"""
views.py:
"""
from django.shortcuts import render
# Create your views here.
def index(request):
i = 123
f = 12.3
str = "Hello Django"
Li = [1, 2, 3]
d = {'username':"Hans", "age":19}
t = (1, 2, 3, 4)
set01 = {1, 2, 3, 4}
bool_value = True
return render(request,'index.html',{"i":i,"f":f,"str":str,"Li":Li,"d":d,'t':t, "set01":set01,'bool_value':bool_value})
"""
# 方法二:使用locals函数
# 在views.py 中给前端页面传值每个都要写,在值特别多的时候不方便,可以使用locals函数
"""
return render(request,"index.html",locals())
"""
locals() 获取全部局部变量:
{'request': <WSGIRequest: GET '/index/'>, 'i': 123, 'f': 12.3, 'str': 'Hello Django', 'Li': [1, 2, 3], 'd': {'username': 'Hans', 'age': 19}, 't': (1, 2, 3, 4), 'set01': {1, 2, 3, 4}, 'bool_value': True},然后全部传给前端页面。
两者的优缺点:
方法一,可以精确传值,不会造成资源浪费,但传的值多的时候书写不方便
方法二, 书写方便,但是会造成资源浪费。
8.2 传函数名
# 前端:
"""
<body>
<p>{{ foo }}</p>
</body>
"""
# views.py:
"""
from django.shortcuts import render
# Create your views here.
# 定义函数
def index(request):
def foo():
print("hello")
return "Hello Django"
return render(request,"index.html",{"foo":foo}) # 给前端传递,前面拿到的是函数的返回值。
"""
使用模板语法传函数的时候,不支持带参数
8.3 传类名
# 前端:
"""
<body>
<p>{{ MyClass }}</p>
<p>{{ obj }}</p>
<p>{{ obj.get_self }}</p>
<p>{{ obj.get_cls }}</p>
<p>{{ obj.get_static }}</p>
</body>
"""
#views.py
"""
from django.shortcuts import render
# Create your views here.
def index(request):
class MyClass(object):
def get_self(self):
return "绑定给对象的方法"
@classmethod
def get_cls(cls):
return "绑定给类的方法"
@staticmethod
def get_static():
return "普通的函数"
obj = MyClass()
print(locals())
return render(request,"index.html",{"MyClass":MyClass,"obj":obj})
"""
或直接写:
return render(request,"index.html",locals())
总结
传递函数名和类名都会自动加括号调用(模板语法不支持额外的传参数)
9. 模板语法获取值Django
中模板语法取值只用.
# views.py
"""
from django.shortcuts import render
# Create your views here.
def index(request):
Li = [1, 2, 3]
d = {'username':"Hans", "age":19}
return render(request,'index.html',locals())
"""
# 前端:
"""
<ul>{{ Li.1}</ul> 拿列表第二个值
<ul>{{ set01.age}}</ul> 拿年龄
"""
10. 模板语法过滤器
过滤器的符号是管道符:|
,将管道符左侧的数据当做第一个参数。
# views.py:
"""
from django.shortcuts import render
# Create your views here.
def index(request):
i = 123
str = "Hello Django"
Li = [1, 2, 3]
d = {'username':"Hans", "age":19}
bool_value = True
bool_var = False
import datetime
ctime = datetime.datetime.now()
file_size = 409600
h = "<h1>Hello</h1>"
from django.utils.safestring import mark_safe
h1 =mark_safe("<h1>Django</h1>") #后端也可以直接写HTML语法返回给前端了
return render(request,"index.html",locals())
"""
# 前端:
"""
<body>
<p>过滤器:将管道符左侧的数据当做第一个参数</p>
<p>统计长度:{{ str|length }}</p>
<p>加法:{{ i|add:10000 }}</p>
<p>字符串拼接:{{ str|add:"HAHA" }}</p>
<p>日期格式:{{ ctime|date:"Y-m-d" }}</p>
<p>默认值:{{ bool_value|default:"哈哈" }}</p>
<p>默认值:{{ bool_var|default:"哈哈" }}</p>
<p>文件大小:{{ file_size|filesizeformat }}</p>
<p>截取文本(截6个字符,包括3个点):{{ str|truncatechars:6 }}</p>
<p>截取文本(截1个单词,不包括3个点):{{ str|truncatewords:1 }}</p>
<p>h源信息:{{ h }}</p>
<p>前端把后端传过来的数据(h),格式成HTML格式: {{ h|safe }}</p>
<p>后端传过来的数据(h1)直接为HTML格式显示: {{ h1 }}</p>
</body>
"""
11. 模板语法标签(流程控制)
# if
{% if var %}
<p>good</p>
{% endif %}
# if else
{% if bool_var %}
<p>var</p>
{% else %}
<p>valu</p>
{% endif %}
# if ... elif ... else
{% if bool_var %}
<p>var</p>
{% elif bool_value %}
<p>value</p>
{% else %}
<p>都没有</p>
{% endif %}
# for
{% for foo in Li %}
<p>foo</p>
{% endfor %}
# for内可以嵌套if
{% for foo in Li %}
<p>foo</p>
{% empty %} # 如果是空的时候,打印empty里的
<p>空值</p>
{% endfor %}
# forloop 打印循环次数
{% for foo in Li %}
<p>foo</p>
<p>{{forloop.counter}}
{% endfor %}
{{}} 变量相关的用
{%%} 逻辑相关的用
# with
{% with d.3.username as name %} # 给d.3.username起别名
{{ name }}
{% endwith %}
这个别名只能在with里面用。
12. 自定义过滤器、标签、inclusion_tag
类似于python
里面的自定义函数
1. 在应用下创建一个名字必须叫"templatetags"文件夹
2, 在上述文件夹内创建一个任意名称的py文件
3, 在该py文件内固定写入:
from django import template
register = template.Library()
12.1 自定义过滤器:
示例:
1,在应用下创建templatetags文件夹
2,在templatetags夹里创建myFilter.py
文件内容:
"""
from django import template
register = template.Library()
@register.filter(name="My") # 过滤器名
def index(a,b):
return a+b
"""
3, views.py
from django.shortcuts import render
def index(request):
i = 123
return render(request,"index.html",locals())
4,前端页面:
<div>
{% load myFilter %}
<p>{{ i |My:100}}</p>
</div>
5. 浏览器显示结果:
223
过滤器只能接受两个参数。
12.2 自定义标签1, 依然是在myFilter.py文件里:
"""
from django import template
register = template.Library()
@register.simple_tag(name='myTag') # 标签名
def foo(a,b,c,d):
return "{%s:%s %s:%s}" % (a,b,c,d)
"""
2, 前端页面:
"""
<div>
{% load myFilter %}
{% myTag 1 "hans" 2 "Hello" %} # 标签传值使用空格分隔
</div>
"""
标签可以接受多个参数
12.3 自定义inclusion_tag前面自定义的过滤器和标签,都是自定义的过滤器函数和标签函数直接返回给前端,自定义inclusion_tag
有些不同。
在myFilter.py文件里:
from django import template
register = template.Library()
@register.inclusion_tag('login.html', name="myInclusion") # inclusion_tag名字
def foo2(n):
l =[]
for i in range(1, n+1):
l.append("第%s页" % i)
return locals()
# login.html
<ul>
{% for foo in l %}
<li>{{ foo }}</li>
{% endfor %}
</ul>
# 前端页面:
<div>
{% load myFilter %}
{% myInclusion 4 %}
</div>
结果:
第1页
第2页
第3页
第4页
总结:
前端要使用自定义过滤器,标签和inclusion_tag
都先要load
.
在某个区域需要反复使用并且数据不固定,适合使用inclusion_tag
.
类似于python
导模块
例如有一个页面会经常用到,不可能每次用到就写一份,可以使用模板导入的方法。
页面导入模板关键字:{%include%}
经常用到的页面form.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<form>
<div class="form-group">
<label for="exampleInputEmail1">Email address</label>
<input type="email" class="form-control" id="exampleInputEmail1" placeholder="Email">
</div>
<div class="form-group">
<label for="exampleInputPassword1">Password</label>
<input type="password" class="form-control" id="exampleInputPassword1" placeholder="Password">
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
</div>
</div>
</div>
</body>
</html>
模板导入:
需要用到模板的页面:
index.html
<body>
<div>
{% include 'form.html' %}
</div>
</body>
14. 模板的继承
示例:
主页home.html
(模板)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
<div class="row">
<nav class="navbar navbar-inverse">
<div class="container-fluid">
<!-- 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="#">Brand</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 class="active"><a href="#">Link <span class="sr-only">(current)</span></a></li>
<li><a href="#">Link</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#">Action</a></li>
<li><a href="#">Another action</a></li>
<li><a href="#">Something else here</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">Separated link</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">One more separated link</a></li>
</ul>
</li>
</ul>
<form class="navbar-form navbar-left">
<div class="form-group">
<input type="text" class="form-control" placeholder="Search">
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
<ul class="nav navbar-nav navbar-right">
<li><a href="#">Link</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#">Action</a></li>
<li><a href="#">Another action</a></li>
<li><a href="#">Something else here</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">Separated link</a></li>
</ul>
</li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
<!--左侧-->
<div class="list-group col-md-2">
<a href="#" class="list-group-item active">
Cras justo odio
</a>
<a href="/compute/" class="list-group-item">电脑</a>
<a href="/phone/" class="list-group-item">手机</a>
<a href="/beauty/" class="list-group-item">beauty</a>
<a href="#" class="list-group-item">Vestibulum at eros</a>
</div>
<!--右侧-->
{% block content %}
<div class="jumbotron col-md-10">
<h1>Hello, world!</h1>
<p>...</p>
<p><a class="btn btn-primary btn-lg" href="#" role="button">Learn more</a></p>
</div>
{% endblock %}
</div>
</div>
</body>
</html>
<!--
{% block content %}
这个区域做了标记,这个区域是可以修改的。
content 这个名字可以随意起
{% endblock %}
-->
电脑(compute.html
)页面继承home.html
{% extends 'home.html' %}
{% block content %}
<div class="row">
<div class="col-xs-6 col-md-3">
<a href="https://www.apple.com.cn/shop/buy-mac/macbook-pro/MK1A3CH/A" class="thumbnail">
<img src="https://store.storeimages.cdn-apple.com/8756/as-images.apple.com/is/mbp16-spacegray-gallery1-202110_GEO_CN?wid=4000&hei=3072&fmt=jpeg&qlt=80&.v=1633656602000">
</a>
</div>
</div>
{% endblock %}
<!--
{% extends 'home.html' %} 继承home.html
{% block content %}
这个区域写homecompute自己的内容
{% endblock %}
-->
手机(phone.html
)页面继承home.html
{% extends 'home.html' %}
{% block content %}
<div class="row">
<div class="col-xs-6 col-md-3">
<a href="#" class="thumbnail">
<img src="https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fexp-picture.cdn.bcebos.com%2F27725684cde34b2ca90aafca0d0e7c75e4f4437e.jpg%3Fx-bce-process%3Dimage%2Fresize%2Cm_lfit%2Cw_500%2Climit_1&refer=http%3A%2F%2Fexp-picture.cdn.bcebos.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1648558900&t=4b7021e3f90edeb7bf92842b1df965cb
">
</a>
</div>
</div>
{% endblock %}
子模板不但能修改被标记的位置,还可以使用模板内容:
{% extends 'home.html'%}
{% block content %}
<div class="row">
<div class="col-xs-6 col-md-3">
<a href="#" class="thumbnail">
<img src="https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.jj20.com%2Fup%2Fallimg%2F4k%2Fs%2F02%2F2110021F21V024-0-lp.jpg&refer=http%3A%2F%2Fimg.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1648559897&t=c5b2a29fac9a42cb0f7019ea13b40d21">
</a>
</div>
</div>
{{ block.super }}
{% endblock %}
<!--
{{ block.super }} 为模板内被标记的区域
-->
模板在标记区域的时候一般有三个区域
- CSS区域
- HTML区域
- JS区域
目的是为了让继承的子模板具有独立的CSS和JS
,增加扩展性
<head>
{% balock css %}
css 样式
{% endblock %}
</head>
<body>
{% balock html %}
html内容
{% endblock %}
{% balock js %}
js 内容
{% endblock %}
</body>
子板也可以使用模板标记的区域的内容:
{{ block.super }}