·
命令:
django-admin startproject 项目名称
创建好后,该项目目录下的各文件说明
● 最外层test1为该项目的容器
● manage.py: 一个可以管理Django项目的命令工具
● 里面一层的test1目录包含这个项目的内容,是一个纯Python包,当引用这个项目内部任何东西时需要用到的包名。(比如test1.urls)
● __ init__.py: 一个空文件,告诉python这个目录是一个python包。
● settings.py: Django的配置文件。
● urls.py: Django项目的URL声明,如同网站的”目录“。
● asgi.py: 作为你的项目的运行在ASGI兼容的Web服务器上的入口。
● wsgi.py: 作为你的项目的运行在WSGI兼容的Web服务器上的入口即web服务器和Django交互的入口。
·
$ python3 manage.py runserver 【ip:端口】(默认端口8000)
启动的是Django自带的用于开发的简易服务器,是一个用python写的轻量级的web服务器,在这个内置服务器中可以快速的开发出想要的东西。
·
一个项目由很多个项目应用组成,每一个应用完成一个特定的功能。
创建应用的命令为:
python3 manage.py startapp 应用名
创建好后,该应用目录下的各文件说明:
● admin.py: 网站后台管理相关的文件。
● apps.py: 该应用程序的配置信息。
● __ init__.py: 一个空文件,告诉python这个目录是一个python包。
● models.py: 写和数据库项目的内容。
● test.py: 写测试代码的文件。
● view.py: 视图文件,接收请求,进行处理,与M和T进行交互,返回应答。定义处理函数,视图函数。
·
当在项目中创建了一个应用后,这个项目本身并不知道该应用的存在,需要在项目的setting.py
中,对应用进行注册。
在setting.py
文件里的INSTALL_APPS()
中增加创建的应用名称:
(我这里新创建的应用名称为booktest)
·
·
ORM模型
全名为 Object-Relation-Mapping
其作用主要是通过类和对象实现对数据库的操作
另一个作用:根据设计的类生成数据库的表
ORM的原理是利用python中的 元类 进行设计,将数据库增删改查的语句封装起来,通过类和对象对方法的引用实现对数据库的操作
·
·
·
● 首先在应用文件里的models.py
文件中设计模型类(必须继承models.Model类)
● 模型类属性命名限制:
1)不能是python的保留关键字。
2)不允许使用连续的下划钱(如: bpud__date),这是由django的查询方式决定的。
3)定义属性时需要指定字段类型,通过字段类型的参数指定选项,语法如下:
属性名=models.字段类型(选项)
详细点击
Django3.1官方文档描述的字段类型和选项大全
常用字段类型:
类型 | 描述 |
---|---|
AutoField | 自动增长的IntegerField ,通常不用指定,不指定时Django会自动创建属性名为id的自动增长属性。 |
BooleanField | 布尔字段,值为True或False。 |
NullBooleanField | 支持Null、True、False三种值。 |
CharField(max_length=最大长度) | 字符串。参数max_length 表示最大字符个数。 |
TextField | 大文本字段,一般超过4000个字符时使用。 |
IntegerField | 整数 |
DecimalField(max_digits=None, decimal_places=None) | 十进制浮点数。参数max_digits 表示总位。参数decimal_places 表示小数位数。 |
FloatField(精度没有DecimalField精确) | 浮点数。参数同上 |
DateField:([auto_now=False, auto_now_add=False]) | 日期。1)参数auto_now 表示每次保存对象时,自动设置该字段为当前时间,用于"最后一次修改"的时间戳,它总是使用当前日期,默认为false。2) 参数auto_now_add 表示当对象第一次被创建时自动设置当前时间,用于创建的时间戳,它总是使用当前日期,默认为false。3)参数auto_now_add 和auto_now 是相互排斥的,组合将会发生错误。 |
TimeField | 时间,参数同DateField。 |
DateTimeField | 日期时间,参数同DateField。 |
FileField | 上传文件字段。 |
ImageField | 继承于FileField,对上传的内容进行校验,确保是有效的图片。 |
·
常用字段选项:
选项名 | 描述 |
---|---|
default | 默认值。设置默认值。 |
primary_key | 若为True,则该字段会成为模型的主键字段,默认值是False,一般作为AutoField的选项使用。 |
unique | 如果为True, 这个字段在表中必须有唯一值,默认值是False。 |
db_index | 若值为True, 则在表中会为此字段创建索引,默认值是False。 |
db_column | 字段的名称,如果未指定,则使用属性的名称。 |
null | 如果为True,表示允许为空,默认值是False。 |
blank | 如果为True,则该字段允许为空白,默认值是False。 |
注:字段选项中
null
是数据库范畴的概念,blank
是后台管理页面表单验证范畴的。
经验:
当修改模型类之后,如果添加的选项不影响表的结构,则不需要重新做迁移,商品的选项中default
和blank
不影响表结构。
·
实例:
·
·
● 将模型类生成表:
之后在booktest
(创建的应用目录)中的migrations
目录下出现了一个文件 0001_initial.py
该文件就是迁移文件
从文件中可以看到程序会自动生成一个id主键
2)执行迁移文件
这项命令的作用是根据迁移文件生成相应的数据库里的表
·
在setting.py
文件中可以看到Django默认使用的数据库表是 sqlite3
·
迁移文件进行迁移后,在数据库中生成的表名依赖于当前的包名
当修改了包名后,由于之前生成的表已经存在,导致在数据库中寻找对应的表会报错
解决方法:通过在模型类中创建猿类Meta ,在元类中定义属性db_table指定表名
class Meta:
db_table = '表名'
例:
class AreaInfo(models.Model):
"""自关联全国地区信息"""
area_name = models.CharField(max_length=100)
# 设置自关联外键,null表示该字段在数据库中可以为空,blank表示后台管理页面中该字段可以为空白,必须指明on_delete
area_parent = models.ForeignKey('AreaInfo', null=True, blank=True, on_delete=models.CASCADE, db_constraint=False)
class Meta:
db_table = 'areainfo'
·
进入项目shell命令
$ python3 manage.py shell
● 首先导入models包下的模型类
● 创建模型类的对象
● 可以通过对象增加实例属性,可以查看实例属性,可以修改实例属性,也可以删除实例属性,例如:
>> from booktest.models import BookInfo
>>> b = BookInfo()
>>> b.btitle = '一本书'
>>> from datetime import date
>>> b.bpub_date = date(1999,1,1)
>>> b.save()
● 最后该实例对象调用save()
方法可以将添加的实例属性进行提交,更改数据库中的内容
·
● 当需要得到或者查询数据库里的内容时,通过 模型类名.objects.get(条件)
可以返回一个对象,透过该对象也可以得到这条信息里面相应的值,也可以对表的内容进行修改(修改后需要调用save()方法才能实现),或者删除该条信息。
>>> b2 = BookInfo.objects.get(id=1)
>>> b2.btitle
'一本书'
>>> b2.bpub_date
datetime.datetime(1999, 1, 1, 0, 0, tzinfo=<UTC>)
>>> b2.btitle = '两本书'
>>> b2.save()
>>> b2.delete()
(1, {'booktest.BookInfo': 1})
·
例:图书类-英雄类
xx = models.ForeignKey()
定义在多的类中。
class HeroInfo(models.Model):
"""英雄类"""
hname = models.CharField(max_length=20)
# 英雄性别,默认False为男性
hgender = models.BooleanField(default=False)
hcomment = models.CharField(max_length=20)
# on_delete=models.CASCADE,级联删除,删除主表的数据时从表数据也删除
hbook = models.ForeignKey('BookInfo', on_delete=models.CASCADE)
shell
>>> h2 = HeroInfo(
... hname = '小红',
... hgender = True,
... hcomment = '撞击',
... hbook = b1)
>>> h2.save()
>>> h2.hbook_id
1
>>> h2.hbook
<BookInfo: BookInfo object (1)>
>>> h2.hbook.btitle
'遮天'
>>> h2.hbook.bpub_date
datetime.datetime(1998, 1, 1, 0, 0, tzinfo=<UTC>)
程序会自动为外键生成一个id, h2.book_id 保存的是h2外键关联BookInfo类的id
这里 h2.hbook 是一个对象。指向的是BookInfo类中当id=1(即BookInfo.objects.get(id=1)
)
·
>>> b1 = BookInfo.objects.get(id=1)
>>> b1.heroinfo_set.all()
<QuerySet [<HeroInfo: HeroInfo object (1)>, <HeroInfo: HeroInfo object (2)>]>
当指明了外键后,BookInfo类中通过对象可以知道每本书对应着哪些英雄,通过 b1.heroinfo_set.all()
可以显示出所关联该对象的英雄对象,这就是一对多。
·
例:新闻类-新闻类型类 体育新闻 国际新闻
xx = models.ManyToManyField()
定义在哪个类中都可以。
·
例:员工基本信息类-员工详细信息类. 员工工号
xx = models.OneToOneField()
定义在哪个类中都可以。
·
·
语言 和 时区 本地化
修改setting.py
文件
·
$ python3 manage.py createsuperuser
键入你想要使用的用户名,然后按下回车键:
Username: admin
然后提示你输入想要使用的邮件地址:
Email address: [email protected]
最后一步是输入密码。你会被要求输入两次密码,第二次的目的是为了确认第一次输入的确实是你想要的密码。
Password: **********
Password (again): *********
Superuser created
successfully.
·
要想在后台管理界面中出现模型类,需要将应用程序里的模型类注册到admin.py
中,告诉管理员该模型类对象需要一个后台接口
·
$ python3 manage.py runserver
打开浏览器,输入 "127.0.0.1:8000/admin/
",可以看见管理员登陆界面
通过管理员页面,可以增加删除用户也可以体验快捷的数据操作
·
解决方法:
通过在models.py模块中,为每个模型类添加魔法方法 __str__(self)
,使得 str(类的实例对象)
返回一个自定义的内容。
如图:
-------------------------------------------------------------------------------------------------------------------------------------------------------
·
在后台管理页面中,可以在 admin.py
中自定义模型类展示的形式
代码:
booktest/admin.py
from django.contrib import admin
from .models import BookInfo, HeroInfo
# Register your models here.
class BookInfoAdmin(admin.ModelAdmin):
"""图书模型管理类"""
list_display = ['id', 'btitle', 'bpub_date']
class HeroInfoAdmin(admin.ModelAdmin):
"""英雄模型管理类"""
list_display = ['id', 'hname', 'hgender', 'hcomment', 'hbook']
admin.site.register(BookInfo, BookInfoAdmin)
admin.site.register(HeroInfo)
-------------------------------------------------------------------------------------------------------------------------------------------------------
·
·
首先将setting.py中默认的数据库换成MySQL
通过MySQL测试链接成功后,将模型类文件进行迁移 (迁移文件在之前已经生成过了)
$ python3 manage.py migrate
如果出现以下错误,则说明系统中没有安装mysqlclient
(vtpy01) user@ubuntu:~/桌面/python/Django_study/test1$ python3 manage.py migrate
Traceback (most recent call last):
File "/home/user/.virtualenvs/vtpy01/lib/python3.8/site-packages/django/db/backends/mysql/base.py", line 15, in <module>
import MySQLdb as Database
ModuleNotFoundError: No module named 'MySQLdb'
The above exception was the direct cause of the following exception:
.
.
.
django.core.exceptions.ImproperlyConfigured: Error loading MySQLdb module.
Did you install mysqlclient?
mysqlclient对于python来说需要一个具体位置的配置信息不好用
这里安装pymysql
,用pymysql
可以伪装成MySQclient
(vtpy01) user@ubuntu:~/桌面/python/Django_study/test1$ pip3 install pymysql
Collecting pymysql
Downloading PyMySQL-0.10.1-py2.py3-none-any.whl (47 kB)
|████████████████████████████████| 47 kB 36 kB/s
Installing collected packages: pymysql
Successfully installed pymysql-0.10.1
安装好后,需要在程序运行前让pymysql
伪装成MySQclient
再次执行迁移文件迁移命令,成功
(vtpy01) user@ubuntu:~/桌面/python/Django_study/test1$ python3 manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, booktest, contenttypes, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
.
.
.
·
·
首先在应用程序的视图文件view.py
中添加一个简单的视图
test1/booktest/view.py
from django.shortcuts import render
from django.http import HttpResponse
# Create your views here.
def index(request):
return HttpResponse("Hello, my first Django on index")
要调用视图,我们需要将其映射到一个URL,因此在应用程序包(app包)下要创建一个urls.py
文件,当作URLconf(url配置文件)。
test1/booktest/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('index', views.index, name='index'),
]
● path() argument: route
route 是一个包含 URL 模式的字符串。在处理请求时,Django 从 urlpatterns 中的第一个模式开始,沿着列表向下移动,将所请求的 URL 与每个模式进行比较,直到找到一个匹配的。
模式不搜索 GET 和 POST 参数或域名。例如,在对https://www.example.com/myapp/的请求中,URLconf 将查找 myapp/。在请求https://www.example.com/myapp/?页面 =3 时,URLconf 也会查找 myapp/。
● path() argument: view
当 Django 找到匹配的模式时,它调用指定的视图函数,第一个参数是 HttpRequest 对象,从路由中“捕获”的任何值都是关键字参数。
● path() argument: kwargs
可以在字典中将任意关键字参数传递给目标视图。
● path() argument: name
name 表示的是route匹配到的URL的一个别名。通过命名 URL,您可以从 Django 的其他地方明确地引用它,特别是在模板中。这个强大的特性允许您在只修改单个文件的同时对项目的 URL 模式进行全局更改。
path('< int(str,):形参 >') or re_path(r'^正则表达式')
path函数中捕获url中的参数需要用到尖括号<> 指定尖括号中的值类型比如
● int
匹配0和正整数
● str
匹配任何空字符串但不包括/
● slug
可理解为注释 匹配任何ascii码包括连接线和下划线
● uuid
匹配一个uuid对象(该对象必须包括破折号—,所有字母必须小写)
取出来的的值会当作实参传入到path()
方法的第二个参数: 引用视图模块里对应的方法
re_path
函数中捕获url中的参数需要用到正则表达式里的分组括号,按照第一个括号分别作为参数传入views里相对应的方法中。
例如,这里会把 (\d+) 里的值当作参数传入:
from django.urls import re_path
re_path(r'^delete/(\d+)$', views.delete, name='delete')
·
·
随后将主项目的URLconf(test1/urls.py
)指向应用里的urls模块
test1/urls.py
from django.contrib import admin
from django.urls import path,include
# 项目的urls文件
urlpatterns = [
path('admin/', admin.site.urls), # 配置项目
path('booktest/', include('booktest.urls')),
]
● include()
函数允许引用其他 URLconf。每当 Django 遇到 include()
时,它都会截断匹配到该点的 URL 的任何部分,并将剩余的字符串发送到包含的 URLconf 中以进行进一步处理。
·
python3 manage.py runserver
运行后
·
在新建的Django项目中,当访问的url或服务器出错时,默认浏览器会显示出DEBUG页面
控制这一显示的选项在 setting.py
中
将DEBUG改为False则可关闭这一显示,并且必须在ALLOWED_HOST中填入允许访问的地址,允许所有地址这里填入 ALLOWED_HOST = ['*']
这里显示的页面是Django提供的404错误页面
404错误显示:当页面显示404错误是指找不到访问的资源,在框架中一般是url没有配置或者url配置错误
500错误显示:当页面显示500错误时,是表示服务器端出现错误,在框架中一般是视图文件或者模板文件出现了错误
·
·
在项目根目录下的模板目录(见下一节模板文件的使用)下创建404.html和500.html文件
● 这里以404.html为例,当访问的资源找不到时,Django会自动调用模板目录里的404.html,如果没有自定义该文件时,会返回Django自带的404错误信息
● 这里request_path
是Django中的魔法属性,表示请求的url路径
·
·
·
● path:一个字符串,表示请求页面的完整路径,不包含域名和参数部分
● META:可通过 request.META['REMOTE_ADDR']
来获取浏览器端的ip地址
● method:一个字符串,表示请求的HTTP方法,常用值包括:‘GET’,‘POST’。
● encoding:一个字符串,表示提交的数据的编码方式。
。如果为None则表示使用浏览器的默认设置,一般为utf-8.
。该属性为可写,可通过修改它,来修改访问表单数据使用的编码,接下来对属性的任何访问都将使用新的encoding值
● GET:QueryDict类型对象,类似于字典,包含get请求方式的所有参数。
● POST:QueryDict类型对象,类似于字典,包含post请求方式的所有参数。
● FILES:一个类似于字典对象,包含所有的上传文件。
● COOKIES:一个标准的Python字典,包含所有的cookie,键和值都为字符串。
● session:一个既可以读又可以写的类似于字典的对象,表示当前的会话,只有当Django启用会话的支持时才可使用
·
·
request
是一个HttpRequest
对象
当进行登录提交的时候通过request对象根据提交的方式可以获得需要的提交内容:
这里提交的方式有两种(GET,POST)
GET: 提交的参数在url中
POST: 提交的参数在请求头中。在对数据安全性要求比较高的时候用POST。
·
在框架中通过request对象获取页面提交的参数内容:
request.POST.get('XXX')
request.GET.get('XXX')
request.POST/GET
返回的是 QueryDict 类型,QueryDict类型是一种类似字典(key-value)的类型,与字典不同的是在QueryDict中可以一次或多次保存多个同名key的值,不会被覆盖,并且通过 getlist(‘key’)
方法可以将该键保存的多个数值全部显示出来,通过 get(‘key’)
方法可以获取对应的value值,当该key有多个值时, get(‘key’)
方法会获取最后一个或者最后一次保存的值。
例:
templates/booktest/login_page.html
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/html">
<head>
<meta charset="UTF-8">
<title>登陆页面</title>
</head>
<body>
<form method="post" action="/login_check">
用户名:<input type="text" name="nameuser"><br/>
密码:<input type="password" name="password"><br/>
<input type="submit" value="登录">
</form>
</body>
</html>
booktest/views.py
def login(request):
"""页面显示"""
return render(request, 'booktest/login_page.html')
def login_check(request):
"""登录按钮点击效验"""
# 通过request对象获取页面提交的参数内容
nameuser = request.POST.get('nameuser')
password = request.POST.get('password')
if nameuser == 'admin' and password == '123':
return redirect('/books')
else:
return redirect('/login')
·
在网页使用的css文件,js文件和图片叫做静态文件。
·
● 配置静态文件所在的物理目录
STATIC_URL
设置访问静态文件对应的url。
STATICFILES_DIRS
设置静态文件所在的物理目录。
·
● 当STATIC_URL改变时,模板文件里对应的对静态文件的链接也必须更改为相应的url,否则会找不到该文件。
对于此情况,可以在模板文件中动态生成静态文件的链接。
1)在模板文件中加载static----> {% load static %}
2)动态获取STATIC_URL,拼接静态文件路径 ----->
代码实例:
show_image.html
{% load static %}
<html lang="en">
<head>
<meta charset="UTF-8">
<title>显示图片title>
head>
<body>
<img src="/static/image/1.jpg"><br/>
动态获取STATIC_URL,拼接静态文件路径:<br/>
<img src="{% static 'image/1.jpg' %}">
body>
html>
·
Ajax:异步的JavaScript
在不重新加载页面的情况下,对页面进行局部的刷新
$.ajax({
'url':请求地址,
'type':请求方式,
'dataType':预期返回的数据格式(一般是json格式),
'data':参数
}).success(function(回调的数据){
//Ajax请求执行完成之后执行的回调函数
})
当浏览器发起Ajax请求给后端,后端在views文件中进行处理,并返回 return JsonResponse(回调的数据)
,将json数据返回给浏览器,浏览器执行success()
回调函数。
注:当Ajax请求出现错误时,只能通过浏览器查看错误信息
·
异步:不等待回调函数的执行完成,直接运行后续代码
同步:等待回调函数执行完成后才继续运行后面的代码
$.ajax({
'url':请求地址,
'type':请求方式,
'dataType':预期返回的数据格式(一般是json格式),
'data':参数,
'async':false
}).success(function(回调的数据){
//Ajax请求执行完成之后执行的回调函数
})
·
login_page.html
<html lang="en" xmlns="http://www.w3.org/1999/html">
<head>
<meta charset="UTF-8">
<title>登陆页面title>
head>
<body>
<form method="post" action="/login_check">
用户名:<input type="text" name="nameuser"><br/>
密码:<input type="password" name="password"><br/>
<input type="submit" value="登录">
form>
body>
html>
views.py
def login(request):
return render(request, 'booktest/login_page.html')
def login_check(request):
nameuser = request.POST.get('nameuser')
password = request.POST.get('password')
print(request.path)
try:
user = UserInfo.objects.get(username=nameuser)
except:
print('aaaaaaaa')
return redirect('/login')
else:
if user.password == int(password):
return redirect('/books')
else:
print(user.password)
return redirect('/login')
·
login_ajax.html
<html lang="en" xmlns="http://www.w3.org/1999/html">
<head>
<meta charset="UTF-8">
<title>ajax登陆页面title>
<script src="/static/js/jquery-1.12.4.min.js">script>
<script>
$(function(){
$('#btnLogin').click(function () {
// 获取用户名和密码
nameuser = $('#nameuser').val()
passqord = $('#password').val()
// 发起post ajax 请求
$.ajax({
'url':'login_ajax_check',
'type':'post',
'data':{'username':nameuser, 'password':passqord},
'datatype':'json'
}).success(function (data) {
// 登录成功{'res':1}
// 登录失败{'res':0}
if (data.res === 0){
$('#errmsg').show().html('用户名输入错误')
}
else if(data.res === 2){
$('#errmsg').show().html('密码输入错误')
}
else {
// 跳转到图书界面
location.href = '/books'
}
})
})
})
script>
<style>
#errmsg{
display: none;
color: red;
}
style>
head>
<body>
<div>
用户名:<input type="text" id="nameuser"><br/>
密码:<input type="password" id="password"><br/>
<input type="button" id="btnLogin" value="登录">
<div id="errmsg">div>
div>
body>
html>
views.py
def login_ajax(request):
return render(request, 'booktest/login_ajax.html')
def login_ajax_check(request):
# 获取用户名和密码
username = request.POST.get('username')
password = request.POST.get('password')
# 进行效验,返回json数据
print(username)
print(password)
try:
u1 = UserInfo.objects.get(username=username)
except:
print('aaaaa')
return JsonResponse({'res': 0})
else:
if u1.password == int(password):
print('bbbbb')
return JsonResponse({'res': 1})
else:
print('cccc')
return JsonResponse({'res': 2})
·
当进行效验的时候会出现下面的界面,是因为在setting.py里的MIDDLEWARE[ ]中的'django.middleware.csrf.CsrfViewMiddleware'
这一选项造成
注:
'django.middleware.csrf.CsrfViewMiddleware'
在Django中默认启用了csrf防护,防止出现csrf伪造攻击网站,只针对post提交
在所有的post提交下面增加一个模板标签
{% csrf_token %}
防御原理:
1)渲染模板文件时在页面生成一个名字叫做csrfmiddlewaretoken
的隐藏域。
2)服务器交给浏览器保存一个名字为 csrftoken 的 cookie 信息。
3)提交表单时,两个值都会发给服务器,服务器进行比对,如果一样,则csrf验证通过,否则失败。
·
.
http协议是无状态的,即下一次去访问一个页面时并不知道上一次这个页面做了什么。
在web中记录信息的两种方式:cookie 和 session
·
是以服务器生成,存储在浏览器端的一小段文本信息
特点:
1)以键值对方式进行存储。
2)通过浏览器访问一个网站时,会将浏览器存储的跟网站相关的所有cookie信息发送给该网站的服务器。 request.COOKIES
3)cookie 是基于域名安全的。
4) cookie 是有过期时间的,如果不指定,默认关闭浏览器后 cookie 就会过期。
代码实例:
views.py
from django.http import HttpResponse
def set_cookie(request):
'''设置cookie信息'''
# 创建一个HttpResponse对象
response = HttpResponse('设置cookie')
# 设置一条cookie信息,设定过期时间为14天后
response.set_cookie('num', 1, max_age=14*24*3600)
return response
`
views.py
from django.http import HttpResponse
def get_cookie(resquest):
'''获得cookie值'''
# request属性COOKIES字典中保存了所有的cookie键值对,通过COOKIES取出对应的值
num = request.COOKIES['num']
return HttpResponse(num)
·
·
views.py
def login_ajax(request):
if 'username' in request.COOKIES:
username = request.COOKIES['username']
else:
username = ''
if 'password' in request:
password = request.COOKIES['password']
else:
password = ''
return render(request, 'booktest/login_ajax.html', {'username': username, 'password': password})
def login_ajax_check(request):
# 获取用户名和密码
username = request.POST.get('username')
password = request.POST.get('password')
# 获取是否记住用户名和密码的选项勾选状态,为None表示没有勾选,为on表示勾选
remember = request.POST.get('remember')
# 进行效验,返回json数据
try:
# 从数据库中获取姓名为username的对象
u1 = UserInfo.objects.get(username=username)
except:
return JsonResponse({'res': 0})
else:
if u1.password == int(password):
response = JsonResponse({'res': 1})
# 当数据库中存在该用户名和密码时设置cookie值,设置过期时间为一周
response.set_cookie('username', username, max_age=7*24*3600)
response.set_cookie('password', password, max_age=7*24*3600)
return response
else:
return JsonResponse({'res': 2})
·
login_ajax.html
<html lang="en" xmlns="http://www.w3.org/1999/html">
<head>
<meta charset="UTF-8">
<title>ajax登陆页面title>
<script src="/static/js/jquery-1.12.4.min.js">script>
<script>
$(function(){
$('#btnLogin').click(function () {
// 获取用户名和密码
nameuser = $('#nameuser').val()
passqord = $('#password').val()
// 发起post ajax 请求
$.ajax({
'url':'login_ajax_check',
'type':'post',
'data':{'username':nameuser, 'password':passqord},
'datatype':'json'
}).success(function (data) {
// 登录成功{'res':1}
// 登录失败{'res':0}
if (data.res === 0){
$('#errmsg').show().html('用户名输入错误')
}
else if(data.res === 2){
$('#errmsg').show().html('密码输入错误')
}
else {
// 跳转到图书界面
location.href = '/books'
}
})
})
})
script>
<style>
#errmsg{
display: none;
color: red;
}
style>
head>
<body>
<div>
用户名:<input type="text" id="nameuser" value="{{ username }}"><br/>
密码:<input type="password" id="password" value="{{ password }}"><br/>
<input type="checkbox" name="remember">记住用户名<br/>
<input type="button" id="btnLogin" value="登录">
<div id="errmsg">div>
div>
body>
html>
实验结果:
·
·
·
·
session存储在服务器端
特点:
1)session 是以键值对进行存储的
2)session 依赖于 cookie。唯一的 标识码保存在 sessionid cookie
中
3)session 也有过期时间,如果不指定,默认两周就会过期
设置session:request.session[‘username’] = ‘xxx’
获取session:request.session[‘username’]
1)以键值对的格式写入session
request.session[‘键’] = 值
2)根据键读取值
request.session.get(‘键’, 默认值)
3)清除所有session,在存储中(数据库中django自建的session表)删除值部分,键(唯一标识码)保留
request.session.clear()
4)清楚session数据,在存储中删除session的整条数据
request.session.flush()
5)删除session中的指定键的值,在存储中只删除某个键对应的值,键保留
del request.session[‘键’]
6)设置会话的超过时间,如果没有指定过期时间则两个星期后过期
request.session.set_expiry(value)
● 如果value是一个整数,会话的session_id cookie
将在value秒没有活动后过期。
● 如果value为0,那么用户会话的session_id cookie
将在用户的浏览器关闭时过期。
● 如果value为None,那么会话的session_id cookie
两周之后过期。
·
代码实例:
views.py
from django.http import HttpResponse
def set_session(request):
request.session['name'] = 'name'
request.session['age'] = 18
return HttpResponse('设置session')
`
from django.http import HttpResponse
def get_session(request):
name = request.session['name']
age = request.session['age']
return HttpResponse(name+':'+str(age))
`
`
代码实例:
views.py
def login_ajax(request):
# 当服务器session表中含有键isLogin时,直接转到图书界面
if request.session.has_key('isLogin'):
return render(request, 'booktest/books_display.html')
else:
if 'username' in request.COOKIES:
username = request.COOKIES['username']
else:
username = ''
if 'password' in request:
password = request.COOKIES['password']
else:
password = ''
return render(request, 'booktest/login_ajax.html', {'username': username, 'password': password})
def login_ajax_check(request):
# 获取用户名和密码
username = request.POST.get('username')
password = request.POST.get('password')
# 获取是否记住用户名和密码的选项勾选状态,为None表示没有勾选,为on表示勾选
remember = request.POST.get('remember')
# 进行效验,返回json数据
try:
# 从数据库中获取姓名为username的对象
u1 = UserInfo.objects.get(username=username)
except:
return JsonResponse({'res': 0})
else:
if u1.password == int(password):
response = JsonResponse({'res': 1})
# 当数据库中存在该用户名和密码时设置cookie值,设置过期时间为一周
response.set_cookie('username', username, max_age=7 * 24 * 3600)
response.set_cookie('password', password, max_age=7 * 24 * 3600)
# 利用session,设置用户已登录
request.session['isLogin'] = True
return response
else:
return JsonResponse({'res': 2})
login_ajax.html
<html lang="en" xmlns="http://www.w3.org/1999/html">
<head>
<meta charset="UTF-8">
<title>ajax登陆页面title>
<script src="/static/js/jquery-1.12.4.min.js">script>
<script>
$(function(){
$('#btnLogin').click(function () {
// 获取用户名和密码
nameuser = $('#nameuser').val()
passqord = $('#password').val()
// 发起post ajax 请求
$.ajax({
'url':'login_ajax_check',
'type':'post',
'data':{'username':nameuser, 'password':passqord},
'datatype':'json'
}).success(function (data) {
// 登录成功{'res':1}
// 登录失败{'res':0}
if (data.res === 0){
$('#errmsg').show().html('用户名输入错误')
}
else if(data.res === 2){
$('#errmsg').show().html('密码输入错误')
}
else {
// 跳转到图书界面
location.href = '/books'
}
})
})
})
script>
<style>
#errmsg{
display: none;
color: red;
}
style>
head>
<body>
<div>
用户名:<input type="text" id="nameuser" value="{{ username }}"><br/>
密码:<input type="password" id="password" value="{{ password }}"><br/>
<input type="checkbox" name="remember">记住用户名<br/>
<input type="button" id="btnLogin" value="登录">
<div id="errmsg">div>
div>
body>
html>
·
·
cookie:记住用户名。 安全性要求不高。
session:涉及到安全性要求比较高的数据。银行卡账户,密码,登陆状态等。
·
cookie保存在客户端
session保存在服务器端
·
cookie的值会直接显示出来
session的值会经过base64编码后保存
·
cookie在Django框架中通过HttpResponse的对象进行设置,获取
session在Django框架中通过request对象进行设置,获取
.
.
模板语言简称为DTL(Django Template Language)
模板变量名由数字,字母,下划线和点组成,不能以下划线开头,使用模板变量:
{{ 模板变量名 }}
解析顺序:
例如: {{ book.btitle }}
1)首先把 book 当成一个字典,把 btitle 当成键名,进行取值 book['btitle’]
2)把book当成一个对象,把 btitle当成属性,进行取值book.btitle
3)把book 当成一个对象,把 btitle 当成对象的方法,进行取值book.btitle
例如: {{ book.0 }}
1)首先把book当成一个字典,把0当成键名,进行取值book[0]
2)把book当成一个列表,把0当成下标,进行取值 book[0]
如果解析失败,则产生内容时用空字符串填充模板变量。
使用模板变量时,“. ”前面的可能是一个字典,可能是一个对象,也可能是一个列表。
·
格式:{% 代码段 %}
for 循环:
{% for i in 列表 %}
xxx // 列表不为空时执行
{% empty %}
xxx // 列表为空时执行
{% endfor %}
可以通过{{ forloop.counter }}得到for循环遍历到了第几次。
if语句:
{% if条件 %}
{% elif条件 %}
{% else %}
{% endif %}
关系比较操作符:> , < , >= , == , !=
注:进行比较操作时,比较操作符两边必须有空格。
逻辑运算: not and or
代码实例:
book.html
<html lang="en">
<head>
<meta charset="UTF-8">
<title>模板标签title>
<style>
.yellow {
background-color: yellow;
}
style>
head>
<body>
{% for i in books %}
{% if i.id <= 1 %}
<li class="yellow">{{ i.btitle }}li>
{% endif %}
{% endfor %}
body>
html>
views.py
def books_play(request):
book_all = Book.objects.all()
return render(request, 'booktest/book.html', {'books': book_all})
·
过滤器用于对模板变量进行操作
过滤器本质是python函数
date:改变日期的显示格式
length:求长度。字符串,列表,元组,字典长度
default:设置模板变量的默认值
格式:模板变量 | 过滤器: 参数
具体见
Django文档–关于模板标签和过滤器
·
代码实例:
books_display.html
<html lang="en" xmlns="http://www.w3.org/1999/html">
<head>
<meta charset="UTF-8">
<title>显示图书信息title>
head>
<h3>图书信息:h3>
<a href="/create">新增a>
<body>
{% for book in books %}
<li><a href="/books/{{ book.id }}">{{ book.btitle }}a>---{{ book.bpub_date|date:'Y年-m月-d日' }} // 对日期的显示增加过滤器
<a href="/delete/{{ book.id }}">删除a>li>
{% endfor %}
<br/>
default过滤器:<br/>
{{ content|default:'默认值' }}
body>
html>
views.py
def books_display(request):
books = BookInfo.objects.all()
return render(request, 'booktest/books_display.html', {'books': books})
实验结果:
·
1)首先,在app路径下创建 templatetags
的python package
2)在templatetags目录中创建自定义的python文件
3)导入包(from django.template import Library
)
4)创建LIbrary()对象
5)创建功能函数并进行装饰器装饰
6)在模板文件中加载自定义的python文件
7)增加过滤器
注:自定义的过滤器函数,至少有一个参数,最多两个参数
代码实例:
booktest/templatetags/filter_test.py
# 自定义过滤器
# 过滤器实质上是python函数
from django.template import Library
# 创建一个Library类的对象
register = Library()
@register.filter
def mod(num):
'''判断num是否为偶数'''
return num % 2 == 0
# 自定义的过滤器函数,至少有一个参数,最多两个
@register.filter()
def mod_val(num, val):
'''判断num是否能被val整除'''
return num % val == 0
books_display.html
<html lang="en" xmlns="http://www.w3.org/1999/html">
{% load filter_test %}
<head>
<meta charset="UTF-8">
<title>显示图书信息title>
head>
<h3>图书信息:h3>
<a href="/create">新增a>
<body>
{% for book in books %}
<li>{{ book.id }}--{{ book.id|mod }}---<a href="/books/{{ book.id }}">{{ book.btitle }}a>
---{{ book.bpub_date|date:'Y年-m月-d日' }}--{{ book.id|mod_val:3 }}
<a href="/delete/{{ book.id }}">删除a>li>
{% endfor %}
<br/>
default过滤器:<br/>
{{ content|default:'默认值' }}
body>
html>
·
单行注释:
{# 注释内容 #}
多行注释:
{% comment %}
注释内容
{% endcomment %}
● 模板注释与html注释的区别
模板注释 通过浏览器检查网页源代码不会显示
html注释 通过浏览器检查网页源代码会显示出来
·
在子模版中需要继承父模板时
{% extends '父模板路径' %}
● 子模版继承父模板后会继承父模板所有模板内容,并且无法直接增加内容,标题也和父模板一样
● 在父模板中增加预留块,在子模版中可通过对预留块的改写增加内容
● 在子模版的预留块中可通过 {{ block.super }}
继承父模板中该预留块的内容
● 对父模板的标题增加预留块,在子模版可以通过该预留块改写子模版的标题
·
实例代码:
parentTemplate.html
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %}父模板{% endblock title %}title>
head>
<body>
<h1>这是父模板h1>
{% block b1 %}
<h3>父模块b1信息h3>
{% endblock b1 %}
{% block b2 %}
{% endblock b2 %}
body>
html>
subTemplate.html
{% extends 'booktest/parentTemplate.html' %}
{% block title %}子模版{% endblock title %}
{% block b1 %}
{{ block.super }}
<h3>这是子模块b1的内容h3>
{% endblock b1 %}
{% block b2 %}
<h3>这是子模块b2的内容h3>
{% endblock b2 %}
·
在模板上下文中的html标签默认是会被转义的
小于号 < 转换为 <
大于号 > 转换为 >
单引号 ‘ 转换为 '
双引号 " 转换为 "
与符号 & 转换为 &
要关闭模板上下文的转义,有两种方法:
● 过滤器
{{ 模板变量|safe }}
● 模板标签
{% autoescape off %}
模板语言代码
{% endautoescape %}
代码实例:
views.py
def escape_test(request):
return render(request, 'booktest/escape_test.html', {'content': 'hello
'})
escape_test.html
<html lang="en">
<head>
<meta charset="UTF-8">
<title>转义title>
head>
<body>
未进行转义时:<br/>
{{ content }}
<br/>
<br/>
用过滤器进行转义:
{{ content| safe }}
<br/>
用模板标签进行转义:
{% autoescape off %}
{{ content }}
{{ content }}
{% endautoescape %}
body>
html>
● 在模板硬编码中的字符串默认不会经过转义,如果需要转义,则需要手动进行转义
硬编码指的是在模板文件中固定死的静态代码
代码实例:
escape_test.html
模板硬编码中的字符串默认不会经过转义:<br/>
{{ tag|default:'<h1>helloh1>' }}<br/>
<br/>
手动进行转义:<br/>
{{ tag|default:'<h1>hello</h1>' }}
body>
html>
·
·
1)在项目根目录中创建templates
目录,该目录下存放模板文件
创建好后,在setting.py 文件中,配置一下模板文件的目录
在setting.py
文件中有一个变量 BASE_DIR 表示这个项目的绝对路径,在本实验环境下为“/home/user/桌面/python/Django_test/test1
”
则在模板路径中应该填入
os.path.join(BASE_DIR, ‘templates’) (路径相加需要用到os.path.join()方法)
2)在应用目录下创建templates
目录,该目录下存放模板文件
在应用目录下创建后无需填写setting.py
文件中的TEMPLATES的‘DIRS’
,但必须让‘APP_DIRS'
设置为True
注:当两种模板目录的存放方式都写了时,Django框架寻找模板文件时优先在项目根目录下寻找templates目录,如果没找到再去应用目录下寻找templates目录,当都没找到的话就会报出错误
TemplateDoesNotExist at xxx/xxx
·
当服务器url正确时,服务器寻找相应的模板文件的顺序为下面红框中内容
·
·
在templates目录下创建与应用同名的目录,比如这里我的应用名是booktest,我就在templates目录下创建一个booktest目录,在每个应用目录下存放属于该应用的模板文件
·
通过render
方法调用模板文件,获取内容,进行模板渲染(将传入的值替换模板文件中相应的值),最后返回给浏览器
·
·
当在浏览器上点击了一个链接,但是想在当前页面中执行功能不进行跳转,则可以使用页面重定向。
页面重定向: 服务器不返回页面,而是告诉浏览器再去请求其他的url地址。
重定向流程:
点击链接–>浏览器发送请求访问相应的url地址–>框架执行相应功能返回给浏览器–>并且告诉浏览器去访问想跳转的url地址–>浏览器访问该url地址
使用方法:
booktest/view.py
# from django.http import HttpResponseRedirect
from django.shortcuts import redirect # 导入重定向函数
def 执行功能(request):
pass
# return HttpResponseRedirect('要跳转的url页面(比如:'/index')')
return redirect('要跳转的url页面(比如:'/index')')
·
·
中间件是Django框架给开发者预留的函数接口,使得可以干预请求和应答的过程
1)在应用路径下新建middleware.py
文件
2)自定义中间件类,导入from django.utils.deprecation import MiddlewareMixin
,中间件类必须继承MiddlewareMixin
3)在中间件类中定义中间件预留函数
● __init __:服务器响应第一个请求的时候调用
● process_request:是在产生request对象,进行url匹配之前调用。
● process_view:是url匹配之后,调用视图函数之前调用。
● process_response:视图函数调用之后,内容返回给浏览器之前调用。
● process_exception:视图函数出现异常,会调用这个函数。
注:如果注册的多个中间件类中包含process_exception函数的时候,调用的顺序和注册的顺序是相反的
更多中间类,查看官网文档
·
例:
4)在settings.py
文件里的 MIDDLEWARE[ ] 中注册中间件类
·
代码实例:
from django.http import HttpResponse
from django.utils.deprecation import MiddlewareMixin
class TestMiddleware(MiddlewareMixin):
"""中间件类"""
def __init__(self, get_response):
'''服务器每次开启时,接收第一个请求时调用。__init__()参数中必须要有get_response'''
self.get_response = get_response
print("__init__")
def process_request(self, request):
'''产生request对象后,与url匹配之前调用'''
print('___process_request____')
def process_view(self, request, view_func, *view_args, **view_kwargs):
'''与url匹配之后,在视图函数调用之前调用'''
print('___process_view___')
def process_response(self, request, response):
'''视图函数调用之后,内容返回浏览器之前调用。最后必须返回return response'''
print('___process_response___')
return response
·
2)在settings.py
中加上MEDIA_ROOT配置
# 配置文件上传保存目录
MEDIA_ROOT = os.path.join(BASE_DIR, 'static/media')
1)在models.py 中设计模型类
class image(models.Model):
'''从后台上传图片'''
# upload_to 指定文件上传的地址
img = models.ImageField(upload_to='static/media/image')
2)在admin.py 中注册该模型类,则可通过后台管理页面上传图片
·
·
1)创建模板文件,定义上传图片的表单
<html lang="en">
<head>
<meta charset="UTF-8">
<title>上传图片title>
head>
<body>
<form method="post" enctype="multipart/form-data" action="{% url 'comment:upload_handle' %}">
{% csrf_token %}
<input type="file" name="pic"><br/>
<input type="submit" value="上传">
form>
body>
html>
2)在视图函数中进行上传图片的处理
。获取上传的图片
。创建一个文件
。获取上传文件的内容并写入创建的文件中
。在数据库中保存上传记录
。返回
def show_upload(request):
'''显示上传图片页面'''
print(settings.MEDIA_ROOT)
return render(request, 'middle_test/show_upload.html')
def upload_handle(request):
'''上传图片处理'''
# 获取上传的图片对象
pic = request.FILES['pic']
# pic.chunks() 返回一个生成器,存储该文件的内容
load = '%s/image/%s' % (settings.MEDIA_ROOT, pic.name)
# 在media/image目录下创建该图片文件,并通过chunks()函数获取文件内容
with open(load, 'wb') as f:
# 这里返回一个生成器需要通过遍历才可以得到内容
for content in pic.chunks():
f.write(content)
# 在数据库中添加该上传记录
image.objects.create(img='static/media/image/%s' % pic.name)
return HttpResponse('上传成功')
·
·
from django.core.paginator import Paginator
paginator = Paginator(数据对象, 按多少条数据进行分页)
Paaginator类对象属性:
属性名 | 说明 |
---|---|
paginator.num_pages | 返回分页之后的总页数 |
paginator.page_range | 返回分页后页码的列表 |
Paaginator类对象方法:
方法名 | 说明 |
---|---|
paginator.page(self, number) | 返回第number页的Page类实例对象 |
Page类对象的属性:
属性名 | 说明 |
---|---|
number | 返回当前页的页码 |
object_list | 返回包含当前页的数据的查询集 |
paginator | 返回对应的Paginator类对象 |
Page类对象的方法:
方法名 | 说明 |
---|---|
has_previous | 判断当前页是否有前一页 |
has_next | 判断当前页是否有后一页 |
previous_page_number | 返回前一页的页码 |
next_page_number | 返回后一页的页码 |
·
·
实例代码:
show_area.html
<html lang="en">
<head>
<meta charset="UTF-8">
<title>显示所有地区title>
head>
<body>
{% for area in page %}
<li>{{ area.area_name }}li>
{% endfor %}
{% if page.has_previous %}
<a href="/show_area{{ page.previous_page_number }}"><上一页a>
{% endif %}
{% for pindex in pageRange %}
{% if pindex == page.number %}
{{ pindex }}
{% else %}
<a href="/show_area{{ pindex }}">{{ pindex }}a>
{% endif %}
{% endfor %}
{% if page.has_next %}
<a href="show_area{{ page.next_page_number }}">下一页>a>
{% endif %}
body>
html>
views.py
rom django.core.paginator import Paginator
def show_area(request, pindex):
'''分页'''
areas = AreaInfo.objects.all()
# 创建Paginator对象,进行分页,每页显示10条
paginator = Paginator(areas, 50)
if pindex == '':
pindex = 1
else:
pindex = int(pindex)
# 获取第一页的内容,page时Page类的实例对象
page = paginator.page(pindex)
# 当页数过多时,进行缩减处理
if pindex-5 < 1:
pageRange = range(1, 11)
elif pindex+5 > paginator.num_pages:
pageRange = range(pindex-5, paginator.num_pages+1)
else:
pageRange = range(pindex-5, pindex+5)
return render(request, 'booktest/show_area.html', locals())
urls.py
from django.urls import re.path
urlpatterns = [
re_path(r'^show_area(?P\d*)$' , views.show_area),
]
·
·
缓存系统需要少量的设置。也就是说,你必须告诉它你的缓存数据应该放在哪里 —— 是在数据库中,还是在文件系统上,或者直接放在内存中。这是一个重要的决定,会影响你的缓存的性能;有些缓存类型比其他类型快。
官方文档说明详细,可分多种情况进行缓存,点击如下链接:
https://docs.djangoproject.com/zh-hans/3.2/topics/cache/
·
·
from django.contrib import admin
from booktest.models import BookInfo, HeroInfo
# Register your models here.
class BookInfoAdmin(admin.ModelAdmin):
list_display = ['id', 'btitle', 'bpub_date']
class HeroInfoAdmin(admin.ModelAdmin):
list_display = ['id', 'hname']
admin.site.register(BookInfo, BookInfoAdmin)
admin.site.register(HeroInfo, HeroInfoAdmin)
·
·
from django.db import models
# Create your models here.
class BookInfo(models.Model):
"""书本信息"""
btitle = models.CharField(max_length=20)
bread = models.IntegerField(default=0)
bpub_date = models.DateTimeField()
isDelete = models.BooleanField(default=False)
class HeroInfo(models.Model):
"""英雄角色信息"""
hname = models.CharField(max_length=20)
hcomment = models.CharField(max_length=20)
isDelete = models.BooleanField(default=False)
# 设置外键,参数中必须指明 on_delete
hbook = models.ForeignKey('BookInfo', on_delete=models.CASCADE)
class AreaInfo(models.Model):
"""自关联全国地区信息"""
area_name = models.CharField(max_length=100)
# 设置自关联外键,null表示该字段在数据库中可以为空,blank表示后台管理页面中该字段可以为空白,必须指明on_delete
area_parent = models.ForeignKey('AreaInfo', null=True, blank=True, on_delete=models.CASCADE, db_constraint=False)
·
from django.urls import path
from booktest import views
urlpatterns = [
path('books', views.books_display, name='books_display'),
path('books/' , views.hero_display, name='hero_display'),
path('create', views.create, name='create'),
path('delete/' , views.delete, name='delete'),
path('area', views.area_view, name='area_view'),
]
·
from django.shortcuts import render
from booktest.models import BookInfo, HeroInfo, AreaInfo
from django.shortcuts import redirect
from django.utils import timezone
from django.http import HttpResponseRedirect
# Create your views here.
def books_display(request):
books = BookInfo.objects.all()
return render(request, 'booktest/books_display.html', {'books': books})
def hero_display(request, bid):
book = BookInfo.objects.get(id=bid)
heros = book.heroinfo_set.all()
return render(request, 'booktest/hero_display.html', {'heros': heros})
def create(request):
b = BookInfo(
btitle='灵剑封魔录',
bread=125,
bpub_date=timezone.now() # 显示当前时间
)
b.save()
return redirect('/books') # 重定向到该url
# return HttpResponseRedirect('/books')
def delete(request, bid):
b = BookInfo.objects.get(id=bid)
b.delete()
return redirect('/books')
def area_view(request):
area = AreaInfo.objects.get(area_name="广州市")
area_parents = area.area_parent.area_name
area_child = area.areainfo_set.all()
return render(request, 'booktest/area_display.html', {'area': area, 'area_parent': area_parents, 'areas_child':area_child})
·
import pymysql
# 指定版本
pymysql.version_info = (1, 4, 13, "final", 0)
# 将pymysql伪装成mysqlclient
pymysql.install_as_MySQLdb()
·
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>显示图书信息</title>
</head>
<h3>图书信息:</h3>
<a href="/create">新增</a>
<body>
{% for book in books %}
<li><a href="/books/{{ book.id }}">{{ book.btitle }}</a>---{{ book.bpub_date }}
<a href="/delete/{{ book.id }}">删除</a></li>
{% endfor %}
</body>
</html>
·
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>英雄角色信息显示</title>
</head>
<body>
<h3>关联英雄角色信息:</h3>
{% for hero in heros %}
<li>{{ hero.hname }}---{{ hero.hcomment }}</li>
{% empty %}
<li>没有关联的英雄角色</li>
{% endfor %}
</body>
</html>
·
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>广东省上下级城市</title>
</head>
<body>
<h1>当前地区</h1>
{{ area.area_name }}
<h1>父级地区</h1>
{{ area_parent }}
<h1>下级地区</h1>
{% for i in areas_child %}
<li>{{ i.area_name }}</li>
{% endfor %}
</body>
</html>
·
from django.contrib import admin
from django.urls import path, include
# 项目的url文件
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('booktest.urls'))
]
·