python 开发 python开发-Django1 智汇君 2025-01-17 开发 python开发-Django1 https://www.bilibili.com/video/BV1aCxNzUEyi?spm_id_from=333.788.player.switch&vd_source=379b5659b7b00bb6caa4cadf9cc37ad6&p=8
https://docs.djangoproject.com/zh-hans/6.0/
项目流程(重要) 创建项目、创建子应用、项目settings.py注册子应用、项目settings.py配置数据库、项目init.py文件里导入数据库并执行相应函数、子应用models.py配置模型、数据库里先创建好对应数据库、迁移模型、定义子应用视图views.py、定义子应用路由urls.py、定义项目路由urls.py
创建项目 1 2 3 4 5 6 7 8 9 10 django-admin start-project project_name project_name> >manage.py # 管理工具/脚本 >子应用 >project_name> settings.py 设置相关 urls.py 路由相关 asgi.py wsgi.py 程序入口
settings.py 1 2 3 4 5 6 7 注册子应用 数据库设置 admin时区、语言 模版路径设置 静态文件路径设置 DEBUG BASE_DIR
运行项目 1 2 python manage.py runserver python manage.py runserver 127.0.0.1:8000
ALLOWED_HOSTS 项目settings.py中配置,配置完成后需要用python manage.py runserver 0:8000来启动,命令中指定具体的ip,则只能用对应ip加端口访问。命令不加ip和端口号则还是以127来访问
1 2 3 4 5 ALLOWED_HOSTS = ['192.168.31.10' ,'127.0.0.1' ]
创建子应用 子应用应用里涉及到了MVT的M、V,T在工程里会讲
要先进入前面创建的项目文件内,再执行创建子应用命令!
1 2 3 4 5 6 7 8 9 10 python manage.py startapp app_name # django-admin startapp xxx一样的 app_name> > migrations(迁移相关)数据库 apps.py(当前子应用相关) tests.py(测试相关) admin.py(后台相关) models.py(模型相关) 与migrations有关联 views.py(视图相关) 增删改查、最重要的文件 与models.py有关联
注册/注册子应用 重复注册会报错
项目settings.py里配置列表:
1 2 3 4 5 6 7 8 9 10 INSTALLED_APPS = [ 'django.contrib.admin' , 'django.contrib.auth' , 'django.contrib.contenttypes' , 'django.contrib.sessions' , 'django.contrib.messages' , 'django.contrib.staticfiles' , 'firstapp' , 'secondapp.apps.SecondappConfig' ]
配置子应用 通过配置对应子应用的配置类进行配置
AppConfig.name属性表示这个配置类是加载到哪个应用的,每个配置类必须包含此属性,默认自动生 成 AppConfig.verbose_name属性用于设置该应用的直观可读的名字,此名字在Django提供的Admin管理站点中会显示
1 2 3 4 5 6 from django.apps import AppConfigclass FirstappConfig (AppConfig ): name = 'firstapp' verbose_name = "第一个应用后台管理"
MODEL模型 ORM面向对象操作数据库
MVT设计模式中的Model,专门负责和数据库交互.
对应(models.py)由于Model中内嵌了ORM框架,所以不需要直接面向数据库编程而是定义模型类,通过 模型关和对象 完成数据库表的 增刪改查
ORM框架就是把数据库表的行与相应的对象建立关联,互相转换.使得数据库的操作面向对象
对数据库的操作转换成对ORM的操作,类一旦定义好,后续使用时只关心对类或者实例的操作
1 2 3 4 5 6 create database mydb charset utf-8; create table mytable( id int primary key, name varchar(10) )charset utf-8;
Django数据库开发的步骤 1.定义模型类2.模型迁移3.操作数据库
定义模型类 字段类型 AutoField:自动增长的IntegerField,通常不用指定,不指定时Django会动创建属性名为id的 自动增长属住
BooleanField布尔字段,值为True或False
NullBooleanField:支持Null、True、False三种值
CharField:字符串,参数max_length表示最大字符个数
TextField:大文本字段,一般超过4000个字符时使用
IntegerField:整数
DecimalField:十进制浮点数,参数max_digits表示总位数,参数decimal_places表示小数位数
FloatField:浮点数
DateField:日期,参数auto_now表示每次保存对象时,自动设置该字段为当前时间,用于”最后一次修改”的时间戳,它总是使用当前日期,默认为False;参数auto_now_add表示当对象第一次被创建时自动设置当前时间,用于创建的时间戳,它总是使用当前日期,默认为False;参数auto_now_add和auto_now是相互排斥的,组合将会发生错误
TimeField:时间,參数同DateField
DateTimeField:日期时间,参数同DateField
FileField:上传文件字段
ImageField:继承于FileField,对上传的内容进行校验,确保是有效的图片
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 from django.db import models""" 1.定义模型类 2.模型迁移 2.1先生成迁移文件(不会在数据库中生成表,只会创建一个 数据表和模型的对应关系) python manage.py makemigratons 2.2再迁移(会在数据库中生成表) python manage.py migrate 3.操作数据库 在哪里定义模型 模型继承自谁就可以 ORM对应的关系: 表-->类 字段-->属性 """ class BookInfo (models.Model): """ 1. ORM 表 --> 类 字段 --> 属性 2.模型类需要继承自models.Model 3.模型类会自动为我们添加(生成)一个主键 4.属性名=属性类型(选项) 属性名:不要使用 python,mysql关健字 不要使用 连续的下划线(__) 属性类型:和mysql的类型类似相似 charFiled 必须设置 max_length null 是否为空 unique 唯一 default 设置默认值 verbose_name 主要是 admin后台显示 """ name = models.CharField(max_length=10 ,unique=True ) pub_date = models.DateField(null=True ) readcount = models.IntegerField(default=0 ) commentcount = models.IntegerField(default=0 ) is_delete = models.BooleanField(default=False ) def __str__ (self ): return self .name class PeopleInfo (models.Model): name = models.CharField(max_length=10 ) gender = models.BooleanField() book = models.ForeignKey(BookInfo, on_delete=models.CASCADE) """ ForeignKey字段缺少了必需的 on_delete参数。这是 Django 2.0 版本之后强制要求的。 # 1. 级联删除(当BookInfo记录被删除时,相关PeopleInfo也被删除) book = models.ForeignKey(BookInfo, on_delete=models.CASCADE) # 2. 保护(禁止删除有关联数据的BookInfo) book = models.ForeignKey(BookInfo, on_delete=models.PROTECT) # 3. 设置为NULL(需要允许字段为NULL) book = models.ForeignKey(BookInfo, on_delete=models.SET_NULL, null=True) # 4. 设置为默认值 book = models.ForeignKey(BookInfo, on_delete=models.SET_DEFAULT, default=1) # 5. 什么都不做(数据库层面可能会出错,不推荐) book = models.ForeignKey(BookInfo, on_delete=models.DO_NOTHING) """ def __str__ (self ): gender_str = "男" if self .gender else "女" return f"{self.name} ({gender_str} ) - 《{self.book.name} 》"
不使用默认表名 默认表名为:子应用名_类名(小写)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class BookInfo (models.Model): """ 1. ORM 表 --> 类 字段 --> 属性 2.模型类需要继承自models.Model 3.模型类会自动为我们添加(生成)一个主键 4.属性名=属性类型(选项) 属性名:不要使用 python,mysql关健字 不要使用 连续的下划线(__) 属性类型:和mysql的类型类似相似 charFiled 必须设置 max_length null 是否为空 unique 唯一 default 设置默认值 verbose_name 主要是 admin后台显示 """ name = models.CharField(max_length=10 ,unique=True ) pub_date = models.DateField(null=True ) readcount = models.IntegerField(default=0 ) commentcount = models.IntegerField(default=0 ) is_delete = models.BooleanField(default=False ) def __str__ (self ): return self .name class Meta : db_table = "bookinfo"
外键 在设置外键时,需要通过on_delete选项指明主表删除数据时,对于外键引用表数据如何处理,在django.db.models中包含了可选常量: CASCADE级联,刪除主表数据时连同一起删除外键表中数据 PROTECT保护,通过抛出ProtectedError异常,来阻止删除主表中被外键应用的数据
SET_NULL设置为NULL,仅在该字段null=True允许为null时可用
SET_DEFAULT设置为默认值,仅在该字段设置了默认值时可用 SET()设置为特定值或者调用特定方法 DO_NOTHING不做任何操作,如果数据库前置指明级联性,此选项会抛出IntegrityError异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class PeopleInfo (models.Model): GENDER_CHOICES = ( (0 ,"male" ), (1 ,"female" ) ) name = models.CharField(max_length=10 ,verbose_name='名字' ) gender = models.SmallIntegerField(choices=GENDER_CHOICES,default=0 ,verbose_name="性别" ) description = models.CharField(max_length=200 ,null=True ,verbose_name="描述" ) book = models.ForeignKey(BookInfo, on_delete=models.CASCADE,verbose_name="书名" ) is_delete = models.BooleanField(default=False ,verbose_name="逻辑删除" ) """ ForeignKey字段缺少了必需的 on_delete参数。这是 Django 2.0 版本之后强制要求的。 # 1. 级联删除(当BookInfo记录被删除时,相关PeopleInfo也被删除) book = models.ForeignKey(BookInfo, on_delete=models.CASCADE) # 2. 保护(禁止删除有关联数据的BookInfo) book = models.ForeignKey(BookInfo, on_delete=models.PROTECT) # 3. 设置为NULL(需要允许字段为NULL) book = models.ForeignKey(BookInfo, on_delete=models.SET_NULL, null=True) # 4. 设置为默认值 book = models.ForeignKey(BookInfo, on_delete=models.SET_DEFAULT, default=1) # 5. 什么都不做(数据库层面可能会出错,不推荐) book = models.ForeignKey(BookInfo, on_delete=models.DO_NOTHING) """ def __str__ (self ): gender_str = "男" if self .gender else "女" return f"{self.name} ({gender_str} ) - 《{self.book.name} 》"
修改数据库 sqlite 主要是一个嵌入式的关系型数据库 主要是在 移动端使用
sqlite 属于小型的关系型数据库
中型数据库:mysql(免费)、postgreql、sqlserver(收费)
大型数据库:oracle(收费)、DB2(收费)
1 2 3 4 5 6 7 8 pip install PyMySQL # 安装驱动 1.在工程的settings.py里设置对应引擎及其配置 2.在工程的init.py文件里导入pymysql,并调用指定方法 3.数据库里手动创建指定数据库(否则报错) create database book charset 'utf8'; 4.做模型迁移 mysql -uroot -pxxx -h 127.0.0.1 --port 3306
1 2 3 4 5 6 7 8 9 10 11 12 DATABASES = { 'default' : { 'ENGINE' : 'django.db.backends.mysql' , 'HOST' :'127.0.0.1' , 'PORT' :'3306' , 'USER' :'root' , 'PASSWORD' :'tyy123456' , 'NAME' : "book" , } }
1 2 import pymysqlpymysql.install_as_MySQLdb()
shell python manage.py shell 类似于ipython,可在命令行进行数据库增删改查测试
1 2 3 4 python manage.py shell >>from .models import BookInfo >>books = BookInfo.objects.all () >>books
模型迁移 数据库配置在工程目录下的setttings.py
python manage.py makemigrations
迁移之后,会在迁移文件夹中生成文件(多次修改模型类,多次执行生成迁移文件命令会生成多个迁移文件)
python manage.py migrate
(会在数据库中生成表,数据库中django_migrations表会记录每一个子应用相应表的迁移文件名)
也可以先有数据库中的表,再迁移
代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 from django.db import models""" 1.定义模型类 2.模型迁移 2.1先生成迁移文件(不会在数据库中生成表,只会创建一个 数据表和模型的对应关系) python manage.py makemigratons 2.2再迁移(会在数据库中生成表) python manage.py migrate 3.操作数据库 在哪里定义模型 模型继承自谁就可以 ORM对应的关系: 表-->类 字段-->属性 """ class BookInfo (models.Model): """ 1.主键当前会自动生成 """ name = models.CharField(max_length=10 ) class PeopleInfo (models.Model): name = models.CharField(max_length=10 ) gender = models.BooleanField() book = models.ForeignKey(BookInfo, on_delete=models.CASCADE) """ ForeignKey字段缺少了必需的 on_delete参数。这是 Django 2.0 版本之后强制要求的。 # 1. 级联删除(当BookInfo记录被删除时,相关PeopleInfo也被删除) book = models.ForeignKey(BookInfo, on_delete=models.CASCADE) # 2. 保护(禁止删除有关联数据的BookInfo) book = models.ForeignKey(BookInfo, on_delete=models.PROTECT) # 3. 设置为NULL(需要允许字段为NULL) book = models.ForeignKey(BookInfo, on_delete=models.SET_NULL, null=True) # 4. 设置为默认值 book = models.ForeignKey(BookInfo, on_delete=models.SET_DEFAULT, default=1) # 5. 什么都不做(数据库层面可能会出错,不推荐) book = models.ForeignKey(BookInfo, on_delete=models.DO_NOTHING) """
操作数据库 新增数据 两种方式新增数据,一种需要手动入库,一种自动入库。代码写在views.py里,或者django shell里也行。下面代码第二次启动项目时会报错数据重复!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 from django.shortcuts import renderfrom django.http import HttpResponsefrom .models import BookInfo""" 视图 1.是python函数 2.函数的第一个参数就是 请求 和请求相关的 它是 HttpRequest的实例对象 3.我们必须要返回一个相应相应是HttpResponse的实例对象/于类实例对象 """ def firstapp01 (request ): """ request:请求参数 template_name:哪一个模版 context=None:传给模版的参数,为字典类型 """ context = {"name" :"如花" } return render(request,"index.html" ,context=context) def firstapp02 (request ): books = BookInfo.objects.all () context = {"books" :books} return render(request,"get_all_books.html" ,context=context) book = BookInfo( name = "python" , pub_date = "2020-01-01" ) book.save() BookInfo.objects.create( name = 'java' , pub_date = '2026-01-01' )
查询数据 get、all、count
1 2 3 4 5 6 7 8 9 10 try : book = BookInfo.objects.get(id =10000 ) books = BookInfo.objects.all () count1 = BookInfo.objects.count() count2 = BookInfo.objects.all ().count() except BookInfo.DoesNotExist: pass
filter、exclude
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 """ select from bookinfo where 条件语句 相当于where查询 filter:筛选/过滤 返回n个结果(n = 0/1/n) 返回列表 get:返回1个结果 exclude:排除掉符合条件剩下的结果 相当于not 语法形式(pycharm教学视频IDE是有提示的): 以filter(字段名__运算符=值)为例 """ BookInfo.objects.get(id =1 ) BookInfo.objects.get(id__exact=1 ) BookInfo.objects.filter (id =1 ) BookInfo.objects.filter (id__exact=1 ) BookInfo.objects.filter (name__contains='湖' ) BookInfo.objects.filter (name__endswith='部' ) BookInfo.objects.filter (name__isnull=True ) BookInfo.objects.filter (name__in=[1 ,3 ,5 ]) BookInfo.objects.filter (id__gt=3 ) BookInfo.objects.exclude(id__exact=3 ) BookInfo.objects.exclude(id =3 ) BookInfo.objects.filter (pub_date__year='1980' ) BookInfo.objects.filter (pub_date__gt='1980-1-1' )
F对象
1 2 3 4 5 6 7 8 9 10 """ F对象语法格式 filter(字段名_运算符=F('字段名')) """ from django.db.models import FBookInfo.objects.filter (readcount__gte=F('commentcount' )) BookInfo.objects.filter (readcount__gte=F('commentcount' )*2 )
Q对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 """ Q对象语法格式 OR:Q(x) | Q(x) AND:Q(x) & Q(x) # filter(x).filter(x)和filter(x,x)一样的效果 NOT: ~Q(x) # exclude一样的效果 """ BookInfo.objects.filter (id__gt=2 ).filter (readcount__gt=20 ) BookInfo.objects.filter (id__gt=2 ,readcount__gt=20 ) from django.db.models import QBookInfo.objects.filter (Q(id__gt=2 ) | Q(readcount__gt=20 )) BookInfo.objects.filter (~Q(id__exact=3 ))
聚合函数
1 2 3 4 5 6 7 8 9 10 """ 聚合函数 Sum,Max,Min,Avg,Count 聚合函数需要使用 aggregate 语法形式是:aggragte(Xxx('字段')) """ from django.db.models import Sum,Max,Min,Avg,CountBookInfo.objects.aggregate(Sum('readcount' )) BookInfo.objects.filter ().aggregate()
排序
1 2 3 4 5 6 7 8 """ 排序 order_by() """ BookInfo.objects.all ().order_by('readcount' ) BookInfo.objects.all ().order_by('-readcount' )
关联查询
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 """ 关联查询 书籍和人物的关系是1:n 书籍 中没有任何关于人物的字段 人物 中有关于书籍的字段 book 外键 语法形式: 1. 通过书籍查询人物信息(已知 主表数据,关联查询从表数据) 主表模型(实例对象).关联模型类名小写_set.all() 2. 通过人物查询书籍信息(已知 从表数据,关联查询主表数据) 从表模型(实例对象).外健 """ book=BookInfo.objects.get(id =1 ) book.peopleinfo_set.all () from .models import PeopleInfoperson = PeopleInfo.objects.get(id =1 ) book = person.book book.name
关联查询筛选
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 """ 关联查询的筛选 语法形式 我们需要的是 书籍信息,已知条件是 人物信息 我们需要的是 主表数据,已知条件是 从表信息 filter(关联模型类名小写__字段__运算符=值 我们需要的是 人物信息,已知条件是 书籍信息 我们需要是是 从表数据,已知条件是 主表信息 filter(外健字段运算符=值) """ BookInfo.objects.filter (peopleinfo__name__exact='郭靖' ) BookInfo.objects.filter (peopleinfo__name='郭靖' ) BookInfo.objects.filter (peopleinfo__description__contains='八' ) peoples = PeopleInfo.objects.filter (description__contains='八' ) for people in peoples: people.book.name PeopleInfo.objects.filter (bookname='天龙八部' ) PeopleInfo.objects.filter (bookname__exact='天龙八部' ) PeopleInfo.objects.filter (book__readcount__gt=30 ) books = BookInfo.objects.filter (readcount__gt=30 ) for book in books: book.peopleinfo_set.all ()
查询结果集 all、order_by、filter、exclude都是得到的QuerySet查询结果集,不是简单的列表
查询结果集可以再调用过滤器
查询结果集等价于SQL语句,只有当真正使用时才会在数据库中转换成sql语句执行。也就是view中有些结果集代码没有真正使用时,不用注释也不会影响性能
exists():判断查询集中是否有数据,如果有则返回True,没有则返回False。
查询结果集特点:
惰性执行:只有真正使用时才会去数据库执行sql语句,如:books = BookInfo.objects.all()、shell中执行时当执行>>books回车时,或者循环打印时才会转换成sql语句执行
缓存:使用同一个查询集,第一次使用时会发生数据库的查询,然后Djang0会把结果绥存下来,再次使用这个查询集时会使用缓存的数据,减少了数据库的查询次数。
通过数据库日志验证惰性执行:
查看mysq数据库日志可以查看对数据库的操作记录。mysql日志文件默认没有产生,需要做如下配置:
sudo vi /etc/mysql/mysql.conf.d/mysqld.cnf的68、69行
1 2 general_log_file = /var/log/mysql/mysql.log general_log =1
sudo service mysql restart
sudo tail -f /var/log/mysql/mysql.log
验证缓存:
下面代码前三次执行只有1需要到数据库查询,2和3都是使用缓存。4和5和6都执行了数据库查询
1 2 3 4 5 6 7 8 books = BookInfo.objects.all () [book.id for book in books] 1 [book.id for book in books] 2 [book.id for book in books] 3 [book.id for book in BookInfo.objects.all ()] 4 [book.id for book in BookInfo.objects.all ()] 5 [book.id for book in BookInfo.objects.all ()] 6
限制查询结果集 可以使用下标、切片方式访问查询结果集(下标不支持负数),等同于sql中的limit和offset子句
对查询集进行切片后返回一个新的查询集,不会立即执行查询。
分页 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from django.core.paginator import Paginatorobjects = ['john' ,'paul' ,'george' ,'ringo' ] p = Paginator(objects,2 ) p.count >>4 p.num_pages >>2 p.page_range >>range (1 ,3 ) page1 = p.page(1 ) >>page1.object_list ['john' ,'paul' ] page2 = p.page(2 ) >>page2.object_list ['george' ,'ringo' ] >>page2.has_next() false
1 2 3 4 5 6 7 8 9 10 from django.core.paginator import Paginatorbooks = BookInfo.objects.all () p = Paginator(books,2 ) p1 = p.page(1 ) p1.object_list p1[0 ] p1[1 ]
更新数据 1 2 3 4 5 6 7 8 9 10 11 12 book = BookInfo.objects.get(id =1 ) book.readcount = 20 book.save() BookInfo.objects.filter (id =1 ).update( readcount = 200 , commentcount = 300 )
删除数据 1 2 3 4 5 6 7 8 book = BookInfo.objects.get(id =100 ) book.delete() BookInfo.objects.filter (id =1000 ).delete()
admin后台管理 时区、语言 工程目录下的settings.py文件中可以设置admin的语言、时区
管理员 创建管理员:python manage.py createsuperuser
数据库中某一张表会存储账号密码
让后台能够管理模型 子应用的admin.py中注册相应的模型类
为了使后台能够正常显示每一行数据,模型类需要实现–str–()方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 from django.db import models""" 1.定义模型类 2.模型迁移 2.1先生成迁移文件(不会在数据库中生成表,只会创建一个 数据表和模型的对应关系) python manage.py makemigratons 2.2再迁移(会在数据库中生成表) python manage.py migrate 3.操作数据库 在哪里定义模型 模型继承自谁就可以 ORM对应的关系: 表-->类 字段-->属性 """ class BookInfo (models.Model): """ 1.主键当前会自动生成 """ name = models.CharField(max_length=10 ) def __str__ (self ): return self .name class PeopleInfo (models.Model): name = models.CharField(max_length=10 ) gender = models.BooleanField() book = models.ForeignKey(BookInfo, on_delete=models.CASCADE) """ ForeignKey字段缺少了必需的 on_delete参数。这是 Django 2.0 版本之后强制要求的。 # 1. 级联删除(当BookInfo记录被删除时,相关PeopleInfo也被删除) book = models.ForeignKey(BookInfo, on_delete=models.CASCADE) # 2. 保护(禁止删除有关联数据的BookInfo) book = models.ForeignKey(BookInfo, on_delete=models.PROTECT) # 3. 设置为NULL(需要允许字段为NULL) book = models.ForeignKey(BookInfo, on_delete=models.SET_NULL, null=True) # 4. 设置为默认值 book = models.ForeignKey(BookInfo, on_delete=models.SET_DEFAULT, default=1) # 5. 什么都不做(数据库层面可能会出错,不推荐) book = models.ForeignKey(BookInfo, on_delete=models.DO_NOTHING) """ def __str__ (self ): gender_str = "男" if self .gender else "女" return f"{self.name} ({gender_str} ) - 《{self.book.name} 》"
视图view和url路由分发 ROOT_URLCONF 工程目录的settings.py里:
1 2 3 4 5 ROOT_URLCONF = 'learn_django.urls'
路由规则 1.正则部分推荐使用r,表示字符串不转义
2.路由匹配时不能在开始加反斜杠,推荐在结束位置加反斜杠/
正确:path/、path
错误:/path、/path/
(虽然路田结尾带/能带来上述好处,但是却违背了HTTP中URL表示资源位置路径的设计理念,是否结尾带/以所属公司定义风格为准。)
3.请求url在路由匹配时,只匹配路径部分(协议、域名、请求参数不匹配)
http://127.0.0.1:8000/18/?a=10只匹配18/
工程目录的urls.py 工程目录的urls.py里:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 """ URL configuration for learn_django project. The `urlpatterns` list routes URLs to views. For more information please see: https://docs.djangoproject.com/en/6.0/topics/http/urls/ Examples: Function views 1. Add an import: from my_app import views 2. Add a URL to urlpatterns: path('', views.home, name='home') Class-based views 1. Add an import: from other_app.views import Home 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') Including another URLconf 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import adminfrom django.urls import path,includefrom testapp import viewsimport sysprint (sys.path)""" 1.urlpatterns 是固定写法,它的值是 列表 2.我们在浏览器中输入的路径和会urlpatterns中的每一项顺序进行匹配 如果匹配成功,则直接引导到相应的模块, 如果匹配不成功(把urlpatterns中的每一个都匹配过了),则直接返回404 3.Django 1.x 时代:url()函数 + 正则表达式(r 转义 ^ 严格的开始 $ 严格的结束) urlpatterns = [ url(r'^admin/', admin.site.urls), # 使用正则表达式 url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive), # 正则捕获组 url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive), ] Django 2.0+ 时代:path()和 re_path() from django.urls import path, re_path from django.contrib import admin from . import views urlpatterns = [ path('admin/', admin.site.urls), # 使用简单字符串 path('articles/<int:year>/', views.year_archive), # 路径转换器 re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive), # 正则表达式 ] 4.我们在浏览器中输入的路由 中 哪些部分参与正则匹配? http://ip:port/path/?key=value 我们的http://ip:port/和 get post参数不参与正则匹配 5.如果和当前的某一项匹配成功,则引导到子应用中继续匹配 如果匹配成功,则停止匹配返回相应的视围 如果匹配不成功,则继承和后边的工程中的url的每一项继续匹配,直到匹配每一项, """ urlpatterns = [ path('admin/' , admin.site.urls), path('firstapp/' ,include('firstapp.urls' )), path('secondapp/' ,include('secondapp.urls' )), path('testapp/' ,views.test) ]
子应用的urls.py firstapp子应用的urls.py
1 2 3 4 5 6 7 8 from django.urls import path,includeimport viewsurlpatterns = [ path('firstapp01/' , views.firstapp01), path('firstapp02/' ,views.firstapp02), ]
secondapp子应用的urls.py
1 2 3 4 5 6 7 8 from django.urls import path,includefrom . import viewsurlpatterns = [ path('secondapp01/' ,views.secondapp01), path('secondapp02/' ,views.secondapp02) ]
子应用的views.py firstapp子应用的views.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from django.shortcuts import renderfrom django.http import HttpResponse""" 视图 1.是python函数 2.函数的第一个参数就是 请求 和请求相关的 它是 HttpRequest的实例对象 3.我们必须要返回一个相应相应是HttpResponse的实例对象/于类实例对象 """ def firstapp01 (request ): return HttpResponse("firstapp01" ) def firstapp02 (request ): return HttpResponse("firstapp02" )
secondapp子应用的views.py
1 2 3 4 5 6 7 8 from django.shortcuts import renderfrom django.http import HttpResponsedef secondapp01 (request ): return HttpResponse("secondapp01" ) def secondapp02 (request ): return HttpResponse("secondapp02" )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from django.shortcuts import renderfrom django.http import HttpResponse""" 视图 1.是python函数 2.函数的第一个参数就是 请求 和请求相关的 它是 HttpRequest的实例对象 3.我们必须要返回一个相应相应是HttpResponse的实例对象/于类实例对象 """ def firstapp01 (request ): return HttpResponse("firstapp01" ) def firstapp02 (request ): return HttpResponse("firstapp02" )
reverse 以登录、注册成功后访问同一个页面index为例
给路由取一个别名,方便在views.py中动态获取路径。避免路由名字更改时,urls.py,views.py中多处需要进行修改。
1 2 3 4 5 6 7 8 9 from django.urls import path,include,re_pathfrom . import viewsurlpatterns = [ re_path('^index/$' , views.index, name='index' ), ]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from django.shortcuts import renderfrom django.http import HttpResponsefrom django.urls import reversedef index (request ): path = reverse('index' ) return HttpResponse('index' )
namespace 避免多个子应用中相同路由别名name问题
reverse就是通过 name 来动态获取路径(路由) 如没有设置namespace 则可以通过name来获取 reverse(name) 如有设置namespace 可通过namespace:name获取 reverse(namespace:name)
django 1.x和新版本这里使用有些区别,设置主路由设置namespace后直接在views.py里使用就可以了namespace:name
1 2 3 4 5 6 7 8 9 urlpatterns = [ path('admin/' , admin.site.urls), re_path(r'^' ,include('book.urls' ,namespace='book' )), ]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from django.urls import path,include,re_pathfrom . import viewsapp_name = 'book' urlpatterns = [ re_path('^index/$' , views.index, name='index' ), ]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 from django.shortcuts import renderfrom django.http import HttpResponsefrom django.urls import reversedef index (request ): """ 登陆成功之后需要跳转到首页 注册成功之后需要跳转到首页 """ path=reverse('book:index' ) print (path) return HttpResponse('index' )
redirect 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 from django.shortcuts import redirect, renderfrom django.http import HttpResponsefrom django.urls import reversedef index (request ): """ 登陆成功之后需要跳转到首页 注册成功之后需要跳转到首页 reverse就是通过 name 来动态获取路径(路由) 如没有设置namespace 则可以通过name来获取 reverse(name) 如有设置namespace 可通过namespace:name获取 reverse(namespace:name) """ path=reverse('book:index' ) return HttpResponse('index' ) def home (request ): print (request.POST) return HttpResponse('home' ) def test_redirect (request ): path = reverse('book:index' ) return redirect(path)
1 2 3 4 5 6 7 8 9 10 11 12 13 from django.urls import path,include,re_pathfrom . import viewsapp_name = 'book' urlpatterns = [ re_path('^index/$' , views.index, name='index' ), re_path('^home/$' , views.home, name='home' ), re_path('^test_redirect/$' , views.test_redirect) ]
view查询模型数据 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 from django.shortcuts import renderfrom django.http import HttpResponsefrom .models import BookInfo""" 视图 1.是python函数 2.函数的第一个参数就是 请求 和请求相关的 它是 HttpRequest的实例对象 3.我们必须要返回一个相应相应是HttpResponse的实例对象/于类实例对象 """ def firstapp01 (request ): """ request:请求参数 template_name:哪一个模版 context=None:传给模版的参数,为字典类型 """ context = {"name" :"如花" } return render(request,"index.html" ,context=context) def firstapp02 (request ): books = BookInfo.objects.all () context = {"books" :books} return render(request,"get_all_books.html" ,context=context)
状态保持 浏览器请求服务器是无状态的。 无状态:指一次用户请求时,浏览器、服务器无法知道之前这个用户做过什么,每次请求都是一次新的请求。 无状态原因:浏览器与服务器是使用Socket通信的,服务器将请求结果返回给浏览器之后,会关闭当前的Socket连接,而且服务器也会在处理页面完毕之后销毁页面对象
有时需要保持下来用户浏览的状态,比如用户是否登录过,浏览过哪些商品等实现状态保持主要有两种方式: 。在客户端存储信息使用cookile 。在服务器端存储信息使用Session
cookie的流程(重要)
1.因为是第一次请求,所以没有任何cookie信息(请求头没有cookie信息)
2.服务器接收到请求后,没有接收到cookie信息,它就设置cookie信息 3.cookie信息设置在响应中
4.浏览器接收到这个响应之后(响应头中包含Set-Cookie字段,值为key-value),会发现响应中有cookie信息name=teast 浏览器会将cookie信息保存在浏览器中 5.浏览器第二次及其之后的请求都会携带cookie信息
6.服务器会接收到携带的cookie信息name:itcast
request.COOKIES和response.set_cookie 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 def test_cookie (request ): """ 面试题: 你如何理解cookie的?/你谈一谈cookie? 1.概念 2.流程 3.在开发过程中哪里使用了 4.你在开发过程中遇到什么印象深刻的地方 保存在客户端的数据叫做 cookie cookie是保存在客户端(换浏览器,浏览器和服务端都没有cookie信息了) cookie是基于域名(IP)的 0.概念 1.流程(原理) 第一次请求过程 1我们的浏览器第一次请求服务器的时候,不会携带任何cookie信息 服务器接收到请求之后,发现 请求中没有任何cookie信息 2服务器设置一个cookie,这个cookie设置在相应中 我们的浏览器接收到这个相应之后,发现相应中有cookie信息,浏览器会将cookie信息保存起来 第二次及其之后的过程 当我们的浏览器第二次及其之后的请求都会携带cookie信息 我们的服务器接收到请求之后,会发现请求中携带的cookie信息,这样的话就认识是谁的请求了 2.看效果 在浏览器里删除对应已有的cookie 第一次请求时,请求header里没有cookie信息 服务器view发现没有cookie,为响应设置cookie 浏览器响应header里能看到设置的cookie 第二次请求浏览器请求同域名header里cookie信息 保存在服务器的数据叫做 session """ cookies = request.COOKIES username = request.GET.get('username' ,'defalut' ) response = HttpResponse('test_cookie' ) if not cookies: response.set_cookie(key='username' ,value='ty' ) return response
cookie的过期时间和删除 新版chrome浏览器可以在f12开发者工具,application->storage中查看
过期时间为会话结束时(session)表示浏览器关闭时信息就没了,窗口关闭信息还在
max_age max_age默认是None,单位是秒
1 2 3 4 5 6 7 8 9 10 def test_setcookie_maxage (request ): cookies = request.COOKIES username = request.GET.get('username' ,'defalut' ) response = HttpResponse('test_setcookie_maxage' ) if not cookies: response.set_cookie(key='username' ,value='ty' ,max_age=3600 ) return response
删除cookie 1 2 3 4 5 6 7 8 9 10 def delete_cookie (request ): """ 删除cookie有两种方式 response.delete_cookie(key) response.set_cookie(key,value,max_age=0) 上面种方法本质是它 """ response = HttpResponse('test_setcookie_maxage' ) response.delete_cookie(key='name' ) return response
session的流程 存储在数据库中,如下设置可以写,也可以不写,这是默认存储方式:
1 SESSION_ENGINE='django.contrib.sessions.backends.db'
cookie在客户端容易被删除,且敏感数据在客户端不安全
项目settings.py中已经自动注册session,包括中间件中也有session相关的配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 def set_session (request ): """ 保存在服务器的数据叫做session(更安全) session需要依赖于cookie 如果浏览器禁用了cookie(浏览器里可以禁用),则session不能实现 问题1:我换了浏览器,还能获取到 session信息吗?不可以(后期学习可以) 问题2:我不换浏览器,删除session id,则获取不到session数据 问题3:再去执行 set_sesison 的时候 会重新生成session_id 0.概念 1.流程 第一次请求: 我们第一次请求的时候可以携带一些信息(用户名/密码),cookie中没有任何信息 当我们的服务器接收到这个请求之后,进行用户名和密码的验证,验证没有问题可以设置session信息 在设置session信息的同时(session信息保存在服务器端).服务器会在响应头中设置一个sessionid的cookie信息(Set-Cookie:sessionid:xxx;Max-Age:xxxx;xxx) 客户端(浏览器)在接收到响应之后,会将cookie信息保存起来(保存 sessionid的信息) 第二次及其之后的请求: 第二次及其之后的请求都会携带 包含sessionid的cookie信息(Cookie:sessionid:xxx;xxx) 当服务器接收到这个请求之后,会获取到sessionid信息,然后进行验证,验证成功,则可以获取 session信息(session信息保存在服务器端) """ cookies = request.COOKIES user_id = 666 request.session['user_id' ] = user_id response = HttpResponse('set_session' ) return response def get_session (request ): cookies = request.COOKIES user_id = request.session.get('user_id' ) return HttpResponse('get_session' )
session存储位置 数据库、内存、混合模式
清空所有session(值部分) 清除session数据(整条) 删除session中指定键值 设置session的有效期 类视图 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 """ 类视图 # 注册登录例子 注册和登录使用两个不同的视图 注册和登录使用同一个视图 类视图 是采用的面向对象的思路 1.定义类视图 继承自View(from django.views import View) 不同的请求方式有不同的业务逻辑 类视图的方法 就直接采用 http的请求方式的名字 作为我们的函数名.例如:get,post,put,dele 类视图的方法的第二个参数 必须是请求实例对象 类视图的方法 必须有返回值 返回值是HttpResopnse及其子类 2.类视图的url引导 """ def register (request ): pass def denglu (request ): pass def login (request ): if request.method == 'GET' : pass else : pass from django.views import Viewclass LoginView (View ): def get (self,request ): return HttpRequest('get' ) def post (self,request ): return HttpRequest('post' )
类视图原理(涉及源码分析) 子应用settings.py
views.LoginView.as_view()是返回一个函数,路由匹配时执行返回的函数,会先判断请求方法是否允许,然后类视图里有没有定义相应请求方法,后续(源码阅读)
1 2 3 4 5 6 7 8 9 10 11 12 13 urlpatterns = [ re_path('^index/$' , views.index, name='index' ), re_path('^home/$' , views.home, name='home' ), re_path('^test_redirect/$' , views.test_redirect), re_path('^set_cookie/$' , views.set_cookie), re_path('^test_setcookie_maxage/$' , views.test_setcookie_maxage), re_path('^delete_cookie/$' , views.delete_cookie), re_path('^set_session/$' , views.set_session), re_path('^get_session/$' , views.get_session), re_path('^login/$' , views.LoginView.as_view()), ]
登录验证(涉及源码分析) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 """ 个人中心页面 -- 必须登陆才能显示 GET 方式 展示 个人中心 P0ST 实现个人中心信息的修改 定义类视图 案例 进入个人中心前需要是否已登录验证,django已提供简便登录验证的方法 这里使用多继承 MRO """ from django.contrib.auth.mixins import LoginRequiredMixinclass CenterView (LoginRequiredMixin,View): def get (self,request ): return HttpRequest('个人中心展示' ) def post (self,request ): return HttpRequest('个人中心修改' )
中间件(很少用) Django中的中间件是一个轻量级、底层的插件系统,可以介入Django的请求和响应处理过程,修改Django的输入或输出。中间件的设计为开发者提供了一种无侵入式的开发方式,增强了Django框架的健壮性 我们可以使用中间件,在Django处理视图的不同阶段对输人或输出进行干预,
项目settings.py中的middleware设置
中间件的定义 middleware.py,创建这个文件的位置不需要固定,这里创建在子应用中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 """ 中间件的作用:每次请求和相应的时候都会调用 中间件的定义 中间件的使用:我们可以判断母次请求中是否携带了cookie中某些信息(举例) 请求前的执顺序是按照注册的顺序、请求后/响应后的执行顺序是 注册的倒序 """ def simple_middleware (get_response ): def middleware (request ): print ('before request111' ) response = get_response(request) print ('after request/response111' ) return response return middleware def simple_middleware2 (get_response ): def middleware (request ): print ('before request222' ) response = get_response(request) print ('after request/response222' ) return response return middleware
项目settings.py
1 2 3 4 5 6 7 8 9 10 11 12 MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware' , 'django.contrib.sessions.middleware.SessionMiddleware' , 'django.middleware.common.CommonMiddleware' , 'django.contrib.auth.middleware.AuthenticationMiddleware' , 'django.contrib.messages.middleware.MessageMiddleware' , 'django.middleware.clickjacking.XFrameOptionsMiddleware' , 'book.middleware.simple_middleware' , 'book.middleware.simple_middleware2' ]
模版 Template模版 项目settings.py里设置模版文件夹位置
render():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from django.shortcuts import renderfrom django.http import HttpResponse""" 视图 1.是python函数 2.函数的第一个参数就是 请求 和请求相关的 它是 HttpRequest的实例对象 3.我们必须要返回一个相应相应是HttpResponse的实例对象/于类实例对象 """ def firstapp01 (request ): """ request:请求参数 template_name:哪一个模版 context=None:传给模版的参数,为字典类型 """ context = {"name" :"如花" } return render(request,"index.html" ,context=context) def firstapp02 (request ): return HttpResponse("firstapp02" )
模版语法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 """ 模版的使用 """ from datetime import datetimeclass P (View ): def get (self,request ): username = request.GET.get('username' ) context = { 'username' :username, 'age' :60 , 'birthday' :datetime.now(), 'friends' :['tom' ,'jack' ,'rose' ], 'money' :{ '2019' :12000 , '2020' :13000 , '2021' :14000 }, 'desc' :'<script>alert("hot")</script>' } return render(request,'index.html' ,context=context)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > </head > <body > <a href ="xx" > {{username}}</a > <hr > 我的年龄是 {{age}} {% if age > 100 %} 年龄大于100 {% elif age > 80 %} 年龄(80,100] {% else %} 年龄小于等于80 {% endif %} <hr > 朋友 {{friends}} 女朋友 {{friends.2}} <hr > <ul > {% for item in friends %} <li > {{ forloop.counter }} {{ item }}</li > {% endfor %} </ul > <hr > 第一年月薪 {{moneny.2019}} <hr > {# 我是注释 #} {% comment %} 我也是注释 {% endcomment %} <hr > 我的生日是{{birthday|date:"Y年m月j日 H时i分s秒"}} <br > 我的简介是{{desc|safe}} <br > 我没定义的变量:{{adcdefg|default:"None"}} </body > </html >
1 2 3 4 5 6 7 8 9 10 {{key}} # context里的key {% for book in books %} # value为列表,遍历列表 <li>{{ book.name }}</li> {% endfor %} 取列表里的值 key.0 value为字典,取字典里的值 key.username 条件判断 注释
根据key取值 value为列表 value为字典 if条件判断 for循环 注释 1 2 3 4 5 6 7 {# 模版语法,单行注释,网页不会显示,浏览器查看源码也没有 #} {% comment %} 模版语法,多行注释,网页不会显示,浏览器查看源码也没有 {% endcomment %}
过滤器 使用管道符号|来应用过滤器,用于进行计算、转换操作,可以使用在变量、标签中。
如果过滤器需要参数,则使用冒号:传递参数 变量|过滤器:参款(冒号左右没有空格)
safe:禁用转义,告诉模版这个变量是安全的,可以解释执行
length:长度,返回字符串包含字符的个数,或列表、元组、字典的元素个数
default:默认值,如果变量不存在时则返回默认值
data|default:’默认值’
date:日期,用于对日期类型的值进行字符串格式化,常用的格式化字符如下: Y表示年,格式为4位,y表示两位的年。 m表示月,格式为01,02,12等 d表示日,格式为01,02等 j表示日,格式为1,2等。 H表示时,24进制,h表示12进制的时。 i表示分,为0-59 s表示秒:为0-59 value|date:”Y年m月j日 H时i分s秒”
date 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > </head > <body > <a href ="xx" > {{username}}</a > <hr > 我的年龄是 {{age}} {% if age > 100 %} 年龄大于100 {% elif age > 80 %} 年龄(80,100] {% else %} 年龄小于等于80 {% endif %} <hr > 朋友 {{friends}} 女朋友 {{friends.2}} <hr > <ul > {% for item in friends %} <li > {{ forloop.counter }} {{ item }}</li > {% endfor %} </ul > <hr > 第一年月薪 {{moneny.2019}} <hr > {# 我是注释 #} {% comment %} 我也是注释 {% endcomment %} <hr > 我的生日是{{birthday|date:"Y年m月j日 H时i分s秒"}} <br > 我的简介是{{desc|safe}} </body > </html >
safe 这里不用safe过滤器,script代码会被转义(确保数据没有问题时才使用safe)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 """ 模版的使用 """ from datetime import datetimeclass P (View ): def get (self,request ): username = request.GET.get('username' ) context = { 'username' :username, 'age' :60 , 'birthdaty' :datetime.now(), 'friends' :['tom' ,'jack' ,'rose' ], 'money' :{ '2019' :12000 , '2020' :13000 , '2021' :14000 }, 'desc' :'<script>alert("hot")</script>' } return render(request,'index.html' ,context=context)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > </head > <body > <a href ="xx" > {{username}}</a > <hr > 我的年龄是 {{age}} {% if age > 100 %} 年龄大于100 {% elif age > 80 %} 年龄(80,100] {% else %} 年龄小于等于80 {% endif %} <hr > 朋友 {{friends}} 女朋友 {{friends.2}} <hr > <ul > {% for item in friends %} <li > {{ forloop.counter }} {{ item }}</li > {% endfor %} </ul > <hr > 第一年月薪 {{moneny.2019}} <hr > {# 我是注释 #} {% comment %} 我也是注释 {% endcomment %} <hr > 我的简介是{{desc|safe}} </body > </html >
default 模版里使用没有定义的变量不会报错,只是对应位置什么也不显式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > </head > <body > <a href ="xx" > {{username}}</a > <hr > 我的年龄是 {{age}} {% if age > 100 %} 年龄大于100 {% elif age > 80 %} 年龄(80,100] {% else %} 年龄小于等于80 {% endif %} <hr > 朋友 {{friends}} 女朋友 {{friends.2}} <hr > <ul > {% for item in friends %} <li > {{ forloop.counter }} {{ item }}</li > {% endfor %} </ul > <hr > 第一年月薪 {{moneny.2019}} <hr > {# 我是注释 #} {% comment %} 我也是注释 {% endcomment %} <hr > 我的生日是{{birthday|date:"Y年m月j日 H时i分s秒"}} <br > 我的简介是{{desc|safe}} <br > 我没定义的变量:{{adcdefg|default:"None"}} </body > </html >
模版继承 主要是为了提高代码重用,减轻开发人员的工作量
父模版:
如果发现在多个模板中某些内容相同,那就应该把这段内容定义到父模板中。
标签block:用于在父模板中预留区域,留给子模板填充差异性的内容,名字不能相同。为了更好的可读性,建议给endblock标签写上名字,这个名字与对应的block名字相同,父模板中也可以便用上下文中传递过来的数据,
子模板: 标签盛extends:继承,写在子模板文件的第一行。
预留区域,可以编写默认内容,也可以没有默认内容
子模版不用填充父模版中的所有预留区域,如果子模版没有填充,则使用父模版定义的默认值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > {% block title %}标题部分{% endblock title %}</title > </head > <body > {% block header %} <h1 > 顶部</h1 > {% endblock header %} {% block main %} <h1 > 主体部分</h1 > {% endblock main %} {% block footer %} <h1 > 底部</h1 > {% endblock footer %} </body > </html >
1 2 3 4 5 6 7 8 9 10 11 12 {% extends 'base.html' %} {% block title %} 详情页面 {% endblock title %} {% block main %} <a href ="#" > 点击</a > {% endblock main %} {% block footer %} {% endblock footer %}
Jinja2模版 Jinja2:是Python下一个被广泛应用的模板引擎,是由Python实现的模板语言,他的设计思想来源于Djang0的模板引擎,并扩展了其语法和一系列强大的功能,尤其是Flask框架内置的模板语言
由于django默认模板引擎功能不齐全,速度慢,所以我们也可以在Django中使用jinja2,jinja2宣称比django默认模板引擎快10-20倍
Django主流的第三方APP基本上也都同时支持Django默认模板及jinja2,所以要用jinja2也不会有多少障碍
安装jinja2
配置 项目settings.py设置backend为jinja2(默认为DjangoTemplates)
使用 修改成jinja2引擎之后,使用之前template的语法可能会报错
jinja2没有多行注释
jinja2过滤器也有所不同
for循环看迭代几次是loop
loop.index:当前循环选代的次数(从1开始)
loop.index0:当前循环选代的次数(从0开始)
loop.revindex:到循环结束需要迭代的次数(从1开始)
loop.revindex0:到循环结束需要迭代的次数(从0开始)
loop.first:如果是第一次迭代·为True。
loop.last:如果是最后一次迭代·为True。
loop.length:序列中的项目数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 """ jinja2模版的使用 """ class usejinja2 (View ): def get (self,request ): username = request.GET.get('username' ) context = { 'username' :username, 'age' :60 , 'birthday' :datetime.now(), 'friends' :['tom' ,'jack' ,'rose' ], 'money' :{ '2019' :12000 , '2020' :13000 , '2021' :14000 }, 'desc' :'<script>alert("hot")</script>' } return render(request,'index_jinja2template.html' ,context=context)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > </head > <body > <a href ="xx" > {{username}}</a > <hr > 我的年龄是 {{age}} {% if age > 100 %} 年龄大于100 {% elif age > 80 %} 年龄(80,100] {% else %} 年龄小于等于80 {% endif %} <hr > 朋友 {{friends}} 女朋友 {{friends.2}} <hr > <ul > {% for item in friends %} <li > {{ loop }} {{ item }}</li > {% endfor %} </ul > <hr > 第一年月薪 {{money.2019}} <hr > {# 我是注释 #} <hr > {{ range(5) }} </body > </html >
过滤器 有目的地将默认配置保持在最低限度。如果一个模板写一个请求呈现(例如,使用时render())时,Jinja2后端添加全局request,csrf_input和csrf_token上下文,除此之外,这个后端不会创建一个Django风格的环境,它不知道Django过滤器和标签。要使用特定于Django的APl,必须将它们配置到环境中。
例,您可以myproject/jinja2_env.py(位置、名字都不是固定的)使用此内容创建:
1 2 3 4 5 6 7 8 9 10 11 12 from django.template.defaultfilters import datefrom jinja2 import Environmentdef environment (**option ): env=Environment(**option) env.globals .update({'date' :date }) return env
项目settings.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 TEMPLATES = [ { 'BACKEND' : 'django.template.backends.django.DjangoTemplates' , 'DIRS' : [], 'APP_DIRS' : True , 'OPTIONS' : { 'context_processors' : [ 'django.template.context_processors.request' , 'django.contrib.auth.context_processors.auth' , 'django.contrib.messages.context_processors.messages' , ], }, }, { 'BACKEND' : 'django.template.backends.jinja2.Jinja2' , 'DIRS' : [BASE_DIR / "templates" ], 'APP_DIRS' : False , 'OPTIONS' : { 'environment' :'book.jinja2_env.environment' , 'context_processors' : [ 'django.template.context_processors.request' , 'django.contrib.auth.context_processors.auth' , 'django.contrib.messages.context_processors.messages' , ], }, }, ]
jinja2模版使用过滤器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > </head > <body > <a href ="xx" > {{username}}</a > <hr > 我的年龄是 {{age}} {% if age > 100 %} 年龄大于100 {% elif age > 80 %} 年龄(80,100] {% else %} 年龄小于等于80 {% endif %} <hr > 朋友 {{friends}} 女朋友 {{friends.2}} <hr > <ul > {% for item in friends %} <li > {{ loop }} {{ item }}</li > {% endfor %} </ul > <hr > 第一年月薪 {{ money.2019 }} <hr > {# 我是注释 #} <hr > 我的生日是{{date(birthday,"Y年m月j日 H时i分s秒")}} </body > </html >
pycharm\vscode设置 pycharm:默认的python html template是djangotemplate,编写jinja2模版时可能会显示一些不是错误的错误提示,可以改为jinja2
vscode:可以安装djLint插件,同时还需安装python -m pip install djlint,起到高亮和提示的作用
vscode右下角可以设置当前是什么类型的文档,可以选择为jinja html或者django html(两个都需要安装插件)
或者也可以设置.vscode/settings.json
CSRF CSRF全拼为Cross Site Request Forgery译为跨站请求伪造
CSRF指攻击者盗用了你的身份,以你的名义发送恶意请求·。包括:以你名义发送邮件,发消息,盗取你的账号,甚至于购买商品,虚拟货币转账..
造成的问题:个人隐私泄露以及财产要全,
CSRF 流程
使用短信验证码解决 使用cookie解决 同浏览器有一个同源策略,一个客户端上的网站只有协议、域名、端口都相同的网站才能获取对应网站的cookie信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 """ csrf解决方案: 一:短信验证码 二:隐藏随机码搭配cookie """ class TF (View ): def get (self,request ): cookies = request.COOKIES if not cookies: path = reverse('learn_csrf:index' ) return redirect(path) from django.middleware.csrf import get_token ''' 使用随机码 ''' csrf_token = get_token(request) response = render(request,'learn_csrf/transfer.html' ,context={'csrf_token' :csrf_token}) response.set_cookie('csrf_token' ,csrf_token) return response def post (self,request ): data = request.POST user_sms_code = request.POST.get('csrftoken' ) server_sms_code = request.COOKIES.get('csrf_token' ) if user_sms_code != server_sms_code: return HttpResponse('哥们找到你了,敢转我账,你不知道验证码吧' ) to_account = data.get('to_account' ) money = data.get('money' ) return HttpResponse(f"成功向账户:{to_account} 转账:{money} " ) class INDEX (View ): def get (self,request ): cookies = request.COOKIES if cookies: path = reverse('learn_csrf:transfer' ) return redirect(path) return render(request,'learn_csrf/index.html' ) def post (self,request ): data = request.POST print (data) if data.get('account' )=='ty' and data.get('password' )=='123456' : path = reverse('learn_csrf:transfer' ) response = redirect(path) response.set_cookie(key='username' ,value='ty' ) return response response = HttpResponse('账号\密码错误!' ) return response
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > </head > <body > <form action ="http://127.0.0.1:8000/csrf/transfer/" method ="post" > <p > <input type ="hidden" name ="csrftoken" value ={{csrf_token}} > <label > 对方账号:</label > <input type ="text" name ="to_account" value ="" > </p > <p > <label > 金额:</label > <input type ="text" name ="money" value ="" > </p > <p > <input type ="submit" value ="提交" > </p > </form > </body > </html >
静态文件 项目中的CSS、图片、js都是静态文件,一般会将静态文件放到一个单独的目录中,以方便管理。在html页面中调用时,也需要指定静态文件的路径,Django中提供了一种解析的方式配置静态文件路径,静态文件可以放在项目根目录下,也可以放在应用的目录下,由于有些静态文件在项目中是通用的,所以推荐放在项目的根目录下,方便管理
1 2 3 4 5 6 7 8 9 10 STATIC_URL = 'static/' STATICFILES_DIRS = [ Path.joinpath(BASE_DIR,"static" ) ]
请求HttpRequest和响应HttpResponse postman安装 软件或者浏览器插件
HttpRequest对象 回想一下,利用HTTP协议向服务器传参有几种途径? 1.提取URL的特定部分,如/weather/bejing/2018,可以在服务器端的路由中用正则表达式戳取;
2.查询字符串(query string),形如key1=value1&key2=value2; 3.请求体(body)中发送的数据,比如表单数据、json、xml; 4.在http报文的头(header)中
路由路径中包含的参数 url中的位置参数 1.我们进行正则分组的参数会传递给视图 2.定义视图的时,需要定义变量来接收参数
1 2 3 4 5 6 7 8 url(r'^(\d+)/(\d+)/$' ,detail)
1 2 3 4 5 6 def detail (request,category_id,book_id ): print (category_id,book_id) return HttpResponse('detail' )
关键字参数(推荐) 1 2 3 4 5 6 7 8 url(r'^(?P<category_id>\d+)/(?P<book_id>\d+)/$' ,detail)
1 2 3 4 5 6 def detail (request,category_id,book_id ): print (category_id,book_id) return HttpResponse('detail' )
GET方式传递的查询字符串 https://search.bilibili.com/all?vt=63554966&keyword=%80%9A&from_source=websuggest_search&spm_id_from=333.1007&search_source=5&order=click
以?作为一个分隔
?前边 表示 路由
?后边 表示 get方式传递的参数 称之为 查询字符串
?key=value&key=value…
request.GET得到QueryDict 获取url中传递的参数,使用request.GET得到QueryDict(和普通字典特性相似)
1 2 3 4 5 6 7 8 9 def detail (request,category_id,book_id ): print (category_id,book_id) params = request.GET print (params) return HttpResponse('detail' )
当同一个key有多个值时,获取到的对应value以列表组织(使用普通字典的方式获取对应key对应的多个value时,得到的不是列表,而是列表里的第一个值):xxx?name=ty&age=60&gender=male&name=yy
1 2 3 4 5 6 7 8 9 10 11 12 13 def detail (request,category_id,book_id ): print (category_id,book_id) params = request.GET print (params) print (params["name" ]) print (params.get("name" )) print (parmas.getlist("name" )) return HttpResponse('detail' )
QueryDict:get(key,默认值)、getlist(key,默认值)
Post请求体携带数据 请求体数据格式不固定,可以是表单类型字符串,可以是JSON字符事,可以是XML字符事,应区别对待。
CSRF防护 可以发送请求体数据的请求方式有POST、PUT、PATCH、DELETE,Django默认开启了CSRF防护,会对上请求方式进行CSRF防护验证,在测试时可以关闭CSRF防护机制,方法为在项目settings.py文件中注释掉CSRF中间件
1 2 3 4 5 报错 <p>You are seeing this message because this site requires a CSRF cookie when submitting forms. This cookie is required for security reasons, to ensure that your browser is not being hijacked by third parties.</p> <p>If you have configured your browser to disable cookies, please re-enable them, at least for this site, or for “same-origin” requests.</p>
post发送Form表单数据 postman测试发送post请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 # postman post请求 http://127.0.0.1:8000/home/?a=1&b=2 表单: name ty age 60 服务器: def home(request): print(request.POST) # body = request.body # 报错 # print(body.decode(encoding='utf-8')) return HttpResponse('home') <QueryDict: {'d': ['d'], 'a': ['v']}> 报错
header里会自动添加Content-Type:application/form-data
request.POST得到url中的参数部分,也是得到QueryDict
1 2 3 def home (request ): print (request.POST) return HttpResponse('home' )
post发送json数据 postman测试post请求发送json数据
postman->body->raw->选择json
注意:json数据必须是双引号,且最后一个键值对后面不要引号
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 # postman post请求 http://127.0.0.1:8000/home/?a=1&b=2 json数据: {"name":"ty", "age":60} 服务器: def home(request): print(request.POST) # body = request.body print(body.decode(encoding='utf-8')) return HttpResponse('home') <QueryDict: {}> b'{\r\n "name":"ty",\r\n "age":60\r\n}'
header里会自动添加Content-Type:application/json
request.POST得到空QueryDict
request.body得到传递的json数据,是b’xxx’
请求头携带数据 请求头中携带的数据,使用request.META获取,得到字典数据类型
request.META[‘CONTENT_TYPE’]
CONTENT_TYPE:text/plain get请求
CONTENT_TYPE:application/json post请求携带json数据
使用postman测试,在headers中自定义添加name:ruhua
request.META[‘HTTP_NAME’] 获取到ruhua,系统自动大写key且加前缀HTTP_
其它常用HttpRquest对象属性 method:一个字符串,表示请求使用的HTTP方法,常用值括:GET、POST。 user:请求的用户对象。 path:一个字符串,表示请求的页面的完整路径,不包含域名和参数部分。 encoding:一个字符串,表示提交的数据的编码方式。 如果为None则表示使用浏览器的默认设置,一般为utf-8。 这个属性是可写的,可以通过修改它来修改访问表单数据使用的编码,接下来对属性的任何访问将使用新的encoding值。 FILES:一个类似于字典的对象,包含所有的上传文件,
HttpResponse对象 HttpResponse的content参数只能传递字符串,不要传递 对象,字典等数据
status:HTTP status code must be an integer from 100 to 599,只能使用系统规定的
content_type:是一个MIME类型(Multipurpose internet mail extension)
语法形式是:大类/小类
text/css、text/javascript、text/html
application/json
image/png、image/gif、image/jpeg等
1 return HttpResponse(content='xxx' ,status=200 ,content_type='text/plain' )
JsonResponse 使用HttpResponse返回字典时需要将字典转换成字符串。使用HttpResponse的子类JsonResponse可以直接传字典
1 2 3 4 from django.http import JsonResponsed = {'name' :'tom' ,'age' :999 } return JsonResponse(d)
其它的HttpResponse子类 Django提供了一系列HttpResponse的子类,可以快速设置状态码 · HttpResponseRedirect 301 · HttpResponsePermanentRedirect 302
· HttpResponseNotModified 304
· HttpResponseBadRequest 400
· HttpResponseNotFound 404 · HttpResponseForbidden 403 · HttpResponseNotAllowed 405 · HttpResponseGone 410 · HttpResponseServerError 500
Django-ninja 报错 You called this URL via POST, but the URL doesn’t end in a slash and you have APPEND_SLASH set. Django can’t redirect to the slash URL while maintaining POST data. Change your form to point to 127.0.0.1:8000/learn_csrf/transfer/ (note the trailing slash), or set APPEND_SLASH=False in your Django settings.