另外,博客是博主使用Markdown语法写完后直接导入的,可能会存在外链图片失效、章节重复等问题,为节约时间,博主不打算处理,仅作为个人查询技术细节使用
创建方法:终端输入python manage.py startapp news
,其中news是新的应用名
注册创建的APP,在项目的配置文件中(settings.py)找到INSTALLED_APPS列表,并在该列表中添加项目名,如下:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'music',
'sport',
'news',
]
最后的news即为新添加的应用
创建分布式路由的目的是为了让单个应用有单个应用的专属路由,不用一直在项目主路由中添加地址,正常的逻辑是创建了新的应用后,应该在项目主路由中为该应用配置专属的路由,而这个新建应用的路由则在自己的路由文件中去添加
在项目路由中添加新的应用路由地址,如下:
urlpatterns = [
path('admin/', admin.site.urls),
path('img', views.test_static),
path('music/', include('music.urls')),
path('sport/', include('sport.urls')),
path('news/', include('news.urls')),
]
列表的最后即为news应用的专属路由,而使用include方法,表明以后news的路由应该到news自己的urls.py文件中去添加
在新建应用下,创建一个urls.py来添加本应用的路由地址和对应的视图函数,如下:
from django.urls import path
from news import views
urlpatterns = [
path('index', views.index_view),
]
index是news应用的地址,这种情况下,访问域名127.0.0.1:8000/news/index
,浏览器返回的就是视图函数views中index_view方法所写的内容
模型:
是Python的一个类,要求必须继承自django.db.models.Model, 一个模型类代表数据库中的一张数据表,模型类的每个熟悉代表数据库的一个字段,模型是数据增删改查的接口,用来操作数据库
ORM框架:
对象关系映射,允许使用类和对象对数据库进行操作,避免通过SQL语句操作数据库
可以从cmd终端进入,也可以从编译器终端进入,指令都一样
mysql -uroot -p
接下来在终端输入密码
退出在终端输入quit即可
通过models.py生成数据表,如下
from django.db import models
# Create your models here.
class Book(models.Model):
title = models.CharField("书名", max_length=50, default='')
price = models.DecimalField("定价", max_digits=7, decimal_places=2, default=0.0)
通过上面这个文件,执行后应该会在数据库中生成一张Book数据表,这张表有两个字段,分别是title和price,但仅写完这个文件是不够的,需要在终端执行如下命名:
python manage.py makemigrations
,将应用下的models.py文件生成一个中间件,并保存在migration文件夹中python manage.py migrate
,将每个应用下的migration目录中的中间文件同步回数据库使用内部类给模型赋予属性,比如修改表名
class Meta:
db_table = 'book'
models.Model模型类中的objects对象被继承,增删改查都是通过这个objects对象来实现
终端进入Django shell的命令:
python manage.py shell
第一种方法:
MyModel.models.Model.objects.create()
第二种方法:
创建模型对象实例,调用save()保存
obj = MyModel(attr1=value1, attr2=value2....)
obj.attr = value
obj.save()
objects.all() == select * from tabel,返回QuerySet容器对象,内部存放模型实例
一般情况下,为了更好的显示查询结果,都要重新____str____函数,这是为了设置模型的返回值
以下是查询示例
from bookstore.models import Book
al = Book.models.all()
重写str函数
def __str__(self):
return '%s_%s_%s_%s' % (self.title, self.pub, self.price, self.market_price)
查询结果
, , , , ]>
objects.values(‘列1’, ‘列2’),查询部分列数据,返回QuerySet字典列表,等同于
select lie1, lie2
from Book
objects.values_list(‘列1’, ‘列2’),查询部分列数据,返回QuerySet元组列表
order_by()查询结果排序
ORM综合练习
目的:将数据库中,所有的book信息显示在all_book页面上,最终结果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I3lMPOyo-1652317066151)(C:\Users\HP\AppData\Roaming\Typora\typora-user-images\image-20220429095725708.png)]
创建分布式路由:在主项目的urls.py文件中添加bookstore应用的路由,如下
from django.contrib import admin
from django.urls import path, include
from mysite import views
urlpatterns = [
path('admin/', admin.site.urls),
path('img', views.test_static),
path('music/', include('music.urls')),
path('sport/', include('sport.urls')),
path('news/', include('news.urls')),
path('bookstore/', include('bookstore.urls')),
]
最后一行即为bookstore应用的分布式路由
在bookstore应用中创建该应用的路由,在应用目录下新建一个urls.py,填入一下路由信息
from django.urls import path
from bookstore import views
urlpatterns = [
path('all_book', views.all_book),
]
也就是当我访问http://127.0.0.1:8000/bookstore/all_book
时指向的文件和视图函数
视图函数,在views.py文件中添加查询和渲染网页的代码
from django.shortcuts import render
from .models import Book
# Create your views here.
def all_book(request):
all_book = Book.objects.all()
return render(request, 'bookstore/all_book.html', locals())
使用all()方法查询到所有的数据,并把查到的all_book传递给模板文件夹(templates)下bookstore文件夹中的all_book.html文件,locals()函数将查询到的局部变量转换成字典数据,也就是把all_book转换成字典,讲真,我真没理解这个locals到底怎么用的
模板文件,在模板中填写网页展示的内容,并接受视图函数传递过来的数据,并进行展示
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>查看所有表格title>
<style>
td, th {
border: 1px solid;
}
.mt {border-collapse: collapse;}
style>
head>
<body>
<table class="mt">
<tr>
<th>idth>
<th>titleth>
<th>pubth>
<th>priceth>
<th>market_priceth>
<th>opth>
tr>
{% for book in all_book %}
<tr>
<td>{{ book.id }}td>
<td>{{ book.title }}td>
<td>{{ book.pub }}td>
<td>{{ book.price }}td>
<td>{{ book.market_price }}td>
<td>
<a href="#" >更新a>
<a href="#">删除a>
td>
tr>
{% endfor %}
<tr>tr>
table>
body>
html>
这里关键是要理解那一段for循环的写法
不写CSS样式显示出来的表格很丑,没有边框,我在样式中改了一下边框,在两个地方
其实写到这里,Django的作用和整个操作流程,已经大致显示出来了,有必要做个小结:
filter()方法,返回QuerySet
MyModel.objects.filter(attr1=value1, attr2=valur2…)
多个属性在一起时为“与”关系
exclude(条件),不包含此条件的数据集
get(条件),返回单条数据,如果给定的条件超过一条,或者没有数据,都会报错
查询示例:
Book.objects.filter(id__exact=1)
等同于
select * from Book where id = 1
由字段名__谓词组成
Book.objects.filter(id__in = [1,3,5])
Book.objects.filter(id__range = (20,40))
流程:查(使用get)->改->存(使用save)
单条数据更新的具体示例:
# 单条数据更新
b1 = Book.objects.get(id=1)
b1.price = 22
b1.save()
批量更新,直接将查询结果使用update方法更新
# 批量更新
b1 = Book.objects.filter(price__gt=50)
b1.update(market_price=100)
将所有定价大于50的书市场价均设置为100
前面的图书管理界面上,最后一列“op”绑定了两个空链接,现在先来实现更新功能
一步步来
views.py中添加的内容
def update_book(request, book_id):
try:
book = Book.objects.get(id=book_id)
except Exception as e:
print('--update book error is %s' % (e))
return HttpResponse("书本不存在")
if request.method == 'GET':
return render(request, 'bookstore/update_book.html', locals())
elif request.method == 'POST':
price = request.POST['price']
market_price = request.POST['market_price']
book.price = price
book.market_price = market_price
book.save()
return HttpResponseRedirect('/bookstore/all_book')
这个视图函数还挺复杂的,记录一下逻辑
既然是要更改数据,就说明要像数据库传递数据,那么一般是post请求,但是超链接是get请求,所以实际上要对请求进行判断
首先,如果是get请求,就跳转到路由指定的模板文件上,并通过local()将get到的局部变量打包成字典,传递给模板文件update_book.html,模板文件见下面小节(添加模板文件)
如果是post请求,也就是说要通过页面给后台传递参数了,那么把要实现的功能在这里写好,也就是在这里实现数据的更新
查数据
改数据
存数据
返回HttpResponse实体,代码中是重定向,指定了路由的相对路径,绝对路径应该是http://127.0.0.1:8000/bookstore/all_book
强调一下,这里的return必须写,至于是render、HttpResponse还是HttpResponseRedirect根据业务需求,不然项目不知道下一步该往哪里去
逻辑上已经没什么问题,但有一点要注意,查询book是用的get方法,get方法具有唯一性,极易报错,所以有必要进行异常检测
视图函数中我传递了一个book_id参数,这点非常重要。首先这个参数是我们要查询的书名,其次,路由中也要用到,下面会讲到
实际上就是修改一下表格中“更新”的超链接地址,我上一下完整的内容,后面与此类似,就不上完整的内容了
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>查看所有表格title>
<style>
{#为表格增加边框#}
td, th {
border: 1px solid;
}
{#删掉表格单元格之间的空白#}
.mt {border-collapse: collapse;}
style>
head>
<body>
<table class="mt">
<tr>
<th>idth>
<th>titleth>
<th>pubth>
<th>priceth>
<th>market_priceth>
<th>opth>
tr>
{% for book in all_book %}
<tr>
<td>{{ book.id }}td>
<td>{{ book.title }}td>
<td>{{ book.pub }}td>
<td>{{ book.price }}td>
<td>{{ book.market_price }}td>
<td>
<a href="/bookstore/update_book/{{ book.id }}" >更新a>
<a href="#">删除a>
td>
tr>
{% endfor %}
<tr>tr>
table>
body>
html>
视图函数完成后,就要进行前端页面的设计了,前面的逻辑是点击超链接(get请求)的话,就去渲染网页文件,并向网页传递book参数
update_book.html里的内容
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>更改书籍title>
head>
<body>
<form action="/bookstore/update_book/{{ book.id }}" method="post">
<p>
title <input type="text" value="{{ book.title }}" disabled="disabled" />
p>
<p>
publish <input type="text" value="{{ book.pub }}" disabled="disabled" />
p>
<p>
price <input type="text" value="{{ book.price }}" name="price" />
p>
<p>
market_price <input type="text" value="{{ book.market_price }}" name="market_price" />
p>
<p>
<input type="submit" value="更新" />
p>
form>
body>
html>
看看界面
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DqjbC8Yo-1652317066152)(C:\Users\HP\AppData\Roaming\Typora\typora-user-images\image-20220429142029973.png)]
界面还有待优化,这是CSS的事情,暂时不进行美化
很明显是表单数据,因此使用form标签,form的action参数是表单数据请求指向的地址,也就是路由,这个路由的写法也是用的Django语法
注意get请求向这个网页文件传递了book参数,也就是通过book_id获取到的book信息,因此book.id就是数据库中的id字段,title,pub等等也是,4个input标签中各填入了book的4个参数,通过value匹配,用的也是Django的语法,其中title,pub是不可修改的(“disabled” )
最后一个input标签是提交按钮,也就是点击后发送post请求到数据库,并重定向到视图函数中写好的地址
目前数据库中是有5本书的,也就是说,如果按绝对路径来定义路由的话,要定义5个路由,分别是
path('update_book/1', views.update_book),
path('update_book/2', views.update_book),
path('update_book/3', views.update_book),
path('update_book/4', views.update_book),
path('update_book/5', views.update_book),
这么写明显不合理,如果有100个,是不是要写100行
这里有多种替代方式
这里使用的是path转换器,用path转换器记得在视图函数中传递参数,这点非常重要,前面就已经提到了,视图函数中传递了一个book_id这个参数,视图函数通过这个book_id进行了一系列的操作,使用path路由的时候也要用到,正确的写法如下
urls.py中的内容
from django.urls import path
from bookstore import views
urlpatterns = [
path('all_book', views.all_book),
path('update_book/' , views.update_book),
]
添加了一个path('update_book/
path转换器的精髓就在这里,接收视图函数传递的形参,使用<>来转换,前面是这个数据的类型,后面是参数,有点像java中的泛型
这里算是完成了一个相对初学者来说,比较复杂的功能,涉及到的技术栈有:
单条删除:先get(),然后delete()
批量删除:filter()查找,然后delete()删除
伪删除:在表中添加字段is_active,布尔类型,做伪删除时,把值设为false,显示的时候只显示is_active=True的数据,这样就实现了伪删除操作
ORM查询中,表格中还有个删除功能没实现,现在补充一下
过程如下
class Book(models.Model):
def __str__(self):
return '%s_%s_%s_%s' % (self.title, self.pub, self.price, self.market_price)
title = models.CharField("书名", max_length=50, default='', unique=True)
pub = models.CharField("出版社", max_length=100, default='')
price = models.DecimalField("定价", max_digits=7, decimal_places=2, default=0.0)
market_price = models.DecimalField("零售价", max_digits=7, decimal_places=2, default=0.0)
# info = models.CharField("描述", max_length=100, default='')
is_active = models.BooleanField('是否活跃', default=True)
添加的是最后一行,后面删除实际上是将这个字段设置为false
在views.py中添加以下函数:
def delete_book(request):
book_id = request.GET.get('book_id')
if not book_id:
return HttpResponse('---book_id不存在')
try:
book = Book.objects.get(id=book_id, is_active=True)
except Exception as e:
print('delete book get error %s' % e)
# print('--update book error is %s' % (e))
return HttpResponse("书本不存在")
book.is_active = False
book.save()
return HttpResponseRedirect('/bookstore/all_book')
注意,这里在函数中没有传形参book_id了,说明路由配置不是之前的那种了,这里跟着老师使用了第二种路由配置方式**,就是通过request.GET.get()方法获取请求中的参数,注意,get方法是寻找名为name(本例中为‘book_id’)的GET参数,而且如果参数没有提交,返回一个空的字符串,如果我点击第5条图书,那么请求的链接应该是http://127.0.0.1:8000/bookstore/delte_book?id=5
,也就是说,我的book_id=5**,这一点的理解很重要,不然会陷入一个理解的死循环,就像月读一样,为什么请求链接是这样的呢,那就要看我的模板文件(all_book.html)了。
这里的模板文件只涉及all_book.html,因为删除后直接重定向到这个文件了,所以对比上面的更新要简单一点,在模板文件中修改成下面的内容
<td>
<a href="/bookstore/update_book/{{ book.id }}" >更新a>
<a href="/bookstore/delete_book?book_id={{ book.id }}" onclick="return confirm('是否确认删除');">删除a>
td>
回到上面的问题,我增加了一个传递参数的符号?,表明后面的book_id是要传的参数,其实上面get()方法得到的参数就传到了这里,也就是超链接应该是bookstore/delete_book?book_id=5
然后再回到视图函数,后面是删除操作,说是删除,其实不如理解为修改,因为执行的是伪删除操作,注意修改后需要save一下
注意这里的路由不再是用path转换器了,而是直接传递参数,写法如下
from django.urls import path
from bookstore import views
urlpatterns = [
path('all_book', views.all_book),
path('update_book/' , views.update_book),
path('delete_book', views.delete_book),
]
最后一行就是添加的路由,后面通过?传参,理解上相对第一种更麻烦一点,我推荐使用第一种
先上一下数据库中book表
id | title | price | market_price | pub | is_active |
---|---|---|---|---|---|
1 | python | 25.00 | 50.00 | 清华大学出版社 | 1 |
2 | Django | 70.00 | 75.00 | 清华大学出版社 | 1 |
3 | JQuery | 90.00 | 85.00 | 机械工业出版社 | 1 |
4 | Linux | 80.00 | 65.00 | 机械工业出版社 | 1 |
5 | HTML5 | 90.00 | 105.00 | 清华大学出版社 | 1 |
F对象代表数据库中某条记录的字段的信息,是对数据库中的字段值在不获取的情况下进行操作,可以解决并发的问题,类似于给数据库加锁,把并发事件转为串联事件,第二个作用的用于字段之间的比较,比如比较售价大于定价的书籍,写法如下:
Book.objects.filter(market_price__gt=F('prive'))
使用复杂的逻辑或、逻辑非等操作,可以灵活组合
语法
Book.objects.filter(Q(price__lt=20)|Q(pub="清华大学出版社"))
聚合函数: Sum Avg Count Max Min
语法:Book.objects.aggregate(结果变量名=Sum(列))
结果变量名就是sql中的查询结果别名
示例:
Book.objects.aggregate(res=Count('id'))
返回:
{res: 4}
语法:QuerySet.annotate(结果变量名=Sum(列))
sql中的group by
示例——统计每个出版社有多少本书:
pub_set = Book.objects.values('pub')
pub_set_count = put_set.annotate(mycount=Count('pub'))
语法:Book.objects.raw(sql语句, 拼接参数)
个人对基础的sql语句是比较熟的,因此个人喜欢用原生的数据库操作,按理说也可以用pandas操作
但是原生的有很多缺陷和漏洞,SQL注入
解决方法:把参数放入拼接参数中
语法:
from django.db import connection
with connection.cursor() as cur:
cur.execute('sql语句', '拼接参数')
还是推荐使用ORM
创建超级用户指令
python manage.py createsuperuser
这里需要设置用户名、邮箱和密码
进入用户管理系统地址
http://127.0.0.1:8000/admin
根据上面设置的用户名密码登录,可以进入Django的后台管理系统
目前模型文件(models.py)文件中有Book和Author两个类,也就是数据库中的两张表,如果要自己定义的模型类也能在/admin后台管理系统中显示和管理,需要将自己的类注册到后台管理界面
方法步骤:
from .models import Book
admin.site.register(Book)
可以为后台管理界面添加便于操作的新功能,继承自django.contrib.admin中的ModelAdmin类
什么意思,就是我们自建的模型展现形式很丑,而自带的展示起来更加美观
使用方法:
在应用的admin.py里进行定义,class XXXManager(admin.ModelAdmin)
绑定注册模型管理器和模型类
from django.contrib import admin
from .models import *
# Register your models here.
class BookManager(admin.ModelAdmin):
list_display = ['id', 'title', 'pub', 'price', 'market_price', 'is_active']
admin.site.register(Book, BookManager)
list_display参数是将数据以表格的形式展示是管理后台上,列表中有几个数据,就展示多少个列
除了list_display参数,还有以下几个常用的参数:
模型(models.py)中添加Meta类,见前文**ORM基础字段及选项中**的模型类Meta
示例:
class Book(models.Model):
def __str__(self):
return '%s_%s_%s_%s_%s' % (self.title, self.pub, self.price, self.market_price, self.is_active)
title = models.CharField("书名", max_length=50, default='', unique=True)
pub = models.CharField("出版社", max_length=100, default='')
price = models.DecimalField("定价", max_digits=7, decimal_places=2, default=0.0)
market_price = models.DecimalField("零售价", max_digits=7, decimal_places=2, default=0.0)
# info = models.CharField("描述", max_length=100, default='')
is_active = models.BooleanField('是否活跃', default=True)
class Meta:
verbose_name = '图书' # 英文中的单数
verbose_name_plural = verbose_name # 英文中的复数,如果不该的话,会在中文字符后面多一个s
通常不会把所有的数据都放在同一张表,不易于扩展,如一个学生可以报多个课程,一个课程可以有多个学生报名
语法:OneToOneField(类名,on_delete=xxx),on_delete是级联删除
新概念:
外键——如果A表中的X字段引用了B表中的主键,A表叫做子表,B表叫做主表,X字段称为A表的外键,外键描述的是表之间的关系。级联删除——如员工表中一项数据是部专门ID,部门ID是部门表的主键,如果是级联删除,当删除了部门A的时候,会把所有属于部门A的员工都属给删除。
级联删除on_delete=models.CASCADE实例,步骤,新建应用oto,在设置文件中添加这个应用,然后在oto应用的模型文件中创建author及wife表
from django.db import models
# Create your models here.
class Author(models.Model):
name = models.CharField('姓名', max_length=11)
class Meta:
verbose_name = '作者'
verbose_name_plural = verbose_name
class Wife(models.Model):
name = models.CharField('姓名', max_length=11)
author = models.OneToOneField(Author, on_delete=models.CASCADE) # 级联删除,与Author表共生死
class Meta:
verbose_name = '作者之妻'
verbose_name_plural = verbose_name
创建数据:
from oto.models import *
a1 = Author.objects.create(name='hg') # Author中的表数据创建
w1 = Wife.objects.create(name='zl', author=a1) # wife表中的数据创建
# 还可以用下面的方式创建外键连接
w1 = Wife.objects.create(name='zl', author_id=a1.id)
正向查询方法:
wife = Wife.objects.get(name='zl')
# 关联到wife的丈夫
wife.author.name
反向查询方法:
author = Author.objects.get(name='hg')
# 关联到author的妻子
author.wife.name
反向查询时,没有外键的一方,可以调用反向属性查询到关联的另一方,author虽然没有wife的外键,但Django在创建Wife模型时,默认创建反向属性
一个学生又多个班级,一个班级有多个学生,一本图书只能属于一个出版社,一个出版社可以出版多本图书
一对多需要明确具体角色,在多表上设置外键
创建一对多的语法:
# 一
class A(model.Model):
pass
# 多
class B(model.Model):
attr = models.ForeignKey(A, on_delete=XXX) # 同样需要定义级联删除
## 实例
from django.db import models
# Create your models here.
class Publisher(models.Model):
name = models.CharField('出版社名称', max_length=255)
class Book(models.Model):
title = models.CharField('书名', max_length=255)
publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
终端进入shell向数据库中添加数据:
from otm.models import *
pub1 = Publisher.objects.create(name='清华大学出版社')
Book.objects.create(title='C++', publisher=pub1)
Book.objects.create(title='JAVA', publisher_id=pub1.id)
正向查询与一对一映射的正向查询一致
反向查询:
## 示例
pub1 = Publisher.objects.get(name='清华大学出版社')
books = pub1.book_set.all() # 查找所有出版社为pub1的图书
MySQL创建多对多需要依赖第三张表
Django无需手动创建第三张表
使用models.ManyToManyField()来关联,并创建第三张表,创建表的方式与上面两种映射相似,只是无需级联删除
示例:
from django.db import models
# Create your models here.
class Author(models.Model):
name = models.CharField('姓名', max_length=200)
class Book(models.Model):
title = models.CharField('书名', max_length=200)
author = models.ManyToManyField(Author)
场景,两个老师都写了名为Python的书
终端进入shell向数据库中添加数据:
from mtm.models import *
## 方案一,先创建作者
# 创建作者
author1 = Author.objects.create(name='黄某')
author2 = Author.objects.create(name='朱某')
# 创建图书,并关联作者
# 创建书名,用create方法把图书和第一个作者关联
book1 = author1.book_set.create(title='python')
# 用add方法把图书和第二个作者关联
author2.book_set.add(book1)
## 方案二,先创建图书
# 创建图书
book = Book.objects.create(title='python1')
#hzx和author1都参与了Python1的创作
author3 = book.author.create(name='hzx')
book.author.add(author1)
目前数据库中有三张表,分别是author、book和book_author,以下简称a表、b表和ab表
a表中列名是id和name,b表中列名是id和title,ab表中列名是a_id和b_id
正向查询:
谁有属性查谁
反向查询继续用xxx_set方法
服务器和浏览器端进行的交互称为会话
交互行为的记忆
会话保持技术:cookies和session存储技术
保存在客户端浏览器上的存储空间
特点:
因此,是在网页响应的时候传递的
语法:
HttpResponse.set_cookie(key, value, max_age, expires)
参数名:
不指定max_age和expires时,关闭浏览器失效
获取cookies:
value = request.COOKIES.get('usename')
举个例子:
def get_cookies(request):
value = request.COOKIES.get('usename')
return HttpResponse('usename is %s' % value)
页面响应 usename is hg
删除cookies:
response.delete_cookie(key)
cookies把用户数据存储在浏览器上,而session则把数据存储在服务器上,相对更安全
实现方式:
settings.py中配置(默认配置)
ISTALLED_APPS 添加’django.contrib.sessions’,
中间件中添加’django.contrib.sessions.middleware.SessionMiddleware’
session封装给了request对象,是一个字典
存、取示例:
def set_session(request):
request.session['usename'] = 'zl'
return HttpResponse('set session is ok')
def get_session(request):
value = request.session['usename']
return HttpResponse('session usename is %s' % value)
在项目的settings.py文件中可以配置session的性能
用户可以在系统中记录自己的日常学习、旅游笔记,用户数据存储在云笔记平台,用户只有在登录后才能使用笔记功能,且只能查阅自己的笔记内容
用户模块(cookie session):
笔记模块(ORM):
为应用添加模型(数据表)
from django.db import models
# Create your models here.
class User(models.Model):
username = models.CharField('用户名', max_length=200, unique=True)
password = models.CharField('密码', max_length=32)
created_at = models.DateTimeField('创建时间', auto_now_add=True)
updated_at = models.DateTimeField('更新时间', auto_now=True)
def __str__(self):
return "用户" + self.username
数据迁移
添加视图函数
from django.http import HttpResponse
from django.shortcuts import render
from .models import *
# Create your views here.
def reg_view(request):
if request.method == 'GET':
return render(request, 'user/register.html')
elif request.method == 'POST':
username = request.POST['username']
password1 = request.POST['password1']
password2 = request.POST['password2']
if password1 != password2:
return HttpResponse('两次密码输入不一致')
old_users = User.objects.filter(username=username)
if old_users:
return HttpResponse('用户名已注册')
User.objects.create(username=username, password=password1)
return HttpResponse('注册成功')
get请求则渲染网页,post请求则检验数据并向数据库中添加注册信息
创建模板文件,在项目应用下创建模板文件夹(cloud_note/user/templates/user)并新建模板文件,并适当美化,这一步可以放在编写视图函数之前
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>注册title>
<style>
.input {
width: 100px;
}
style>
head>
<body>
<form action="/user/reg" method="post">
<p class="input">
用户名:<input type="text" name="username">
p>
<p class="input">
密码:<input type="text" name="password1">
p>
<p class="input">
再次输入密码:<input type="text" name="password2">
p>
<p>
<input type="submit" value="注册">
p>
form>
body>
html>
创建分布式路由
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('user/', include('user.urls')),
]
分布式路由中添加新的路由和视图函数
from django.urls import path
from user import views
urlpatterns = [
# path('admin/', admin.site.urls),
path('reg', views.reg_view),
]
使用哈希算法——给定明文,计算出一段定长的,不可逆的值
修改视图函数
from django.http import HttpResponse
from django.shortcuts import render
from .models import *
import hashlib
# Create your views here.
def reg_view(request):
if request.method == 'GET':
return render(request, 'user/register.html')
elif request.method == 'POST':
username = request.POST['username']
password1 = request.POST['password1']
password2 = request.POST['password2']
if password1 != password2:
return HttpResponse('两次密码输入不一致')
m = hashlib.md5()
m.update(password1.encode()) # 必须转码
password_m = m.hexdigest()
old_users = User.objects.filter(username=username)
if old_users:
return HttpResponse('用户名已注册')
User.objects.create(username=username, password=password_m)
return HttpResponse('注册成功')
将密码存储为哈希MD5值
并发问题,多个用户同时注册统一用户名时,即使有if判断,也会因为并发报错,可以使用try来规避
继续优化视图函数,在创建新的数据时,使用异常检测
try:
user = User.objects.create(username=username, password=password_m)
except Exception as e:
print(e)
return HttpResponse('用户名已注册')
免登陆一天功能
视图函数中增加session的功能
request.session['username'] = username
request.session['id'] = user.id
settings.py中修改session存储时间
SESSION_COOKIE_AGE = 60 * 60 * 24
先写模板文件login.html
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录title>
<style>
.input {
width: 100px;
}
style>
head>
<body>
<form action="/user/login" method="post">
<p class="input">
用户名<input type="text" name="username">
p>
<p class="input">
密码<input type="password" name="password">
p>
<p><input type="checkbox" name="remember_me">记住用户名p>
<p><input type="submit" value="登录">p>
form>
body>
html>
我在脱离教程独自写这个简单界面的时候犯了一个非常低级的错误,就是没有写form表单标签,上来就直接input,导致怎么点submit按钮都没反应,这再一次验证了form表单的作用
另外,这里还添加了一个记住用户的CheckBox,后面会用到
添加路由
urlpatterns = [
# path('admin/', admin.site.urls),
path('reg', views.reg_view),
path('login', views.login_view),
]
创建视图函数
def login_view(request):
if request.method == 'GET':
return render(request, 'user/login.html')
if request.method == 'POST':
username = request.POST['username']
password = request.POST['password']
# remember_me = request.POST['remember_me']
m = hashlib.md5()
m.update(password.encode())
password_m = m.hexdigest()
try:
# 取用户名,建议用get,也可以用filter,如果用filter,则应写成user = User.objects.filter(username=username)[0],
# 因为filter查出来的是一个集合Queryset
user = User.objects.get(username=username)
except Exception as e:
print(e)
return HttpResponse('用户名或密码错误')
# if user:
# if password_m != user.password:
# return HttpResponse('密码错误')
# else:
# return HttpResponse('用户名不存在')
if password_m != user.password:
return HttpResponse('用户名或密码错误')
# 记住会话状态
request.session['username'] = username
request.session['id'] = user.id
response = HttpResponse('登录成功')
# 判断用户是否选择了记住用户名
if 'remember_me' in request.POST:
response.set_cookie('username', username, 3600 * 24 * 3)
response.set_cookie('id', user.id, 3600 * 24 * 3)
return response
这里的逻辑应该是很清楚的:
判断是get请求还是post请求
post请求下,取出用户名,密码,密码进行哈希转换
判断用户名和密码是否正确,我自认为的逻辑是判断用户名是否存在,然后判断密码是否正确,看我注释起来的一段代码,但是听讲课的那个老师讲,实际中不要这么明确,防止别人窃取信息
记住会话状态,通过设置session和cookie,并修改cookie的存储时间
记住会话状态是一个非常复杂的过程,对于初学者来说,总体来说,有这么几个步骤:
这里有一个很坑的地方:
如果我的用户名是中文的,勾选记住状态时,会报下面的错:
UnicodeEncodeError: 'latin-1' codec can't encode characters in position 153-154: ordinal not in range(256)
从报错信息来看,说的是编码的问题,我百度了很多方法,都没能解决,但是都指向同一个问题,就是用户名不能是中文名,因此,在注册时,应该禁止用户输入中文字符,所以我在reg_view()视图函数中添加了一段判断用户名是否包含中文字符的代码,如下:
def reg_view(request):
if request.method == 'GET':
return render(request, 'user/register.html')
elif request.method == 'POST':
username = request.POST['username']
password1 = request.POST['password1']
password2 = request.POST['password2']
# 经验证,注册的用户名中不能使用中文,否则登录的时候选择记住用户的话,会报错,所以用户注册时,用户名中不能有中文
for _char in username:
if '\u4e00' <= _char <= '\u9fff':
return HttpResponse('用户名中不能包含中文')
if password1 != password2:
return HttpResponse('两次密码输入不一致')
# 将密码存储为MD5值
m = hashlib.md5()
m.update(password1.encode())
password_m = m.hexdigest()
old_users = User.objects.filter(username=username)
if old_users:
return HttpResponse('用户名已注册')
# 考虑注册时的并发问题,使用异常检测
try:
user = User.objects.create(username=username, password=password_m)
except Exception as e:
print(e)
return HttpResponse('用户名已注册')
# 免登陆功能
request.session['username'] = username
request.session['id'] = user.id
# request.session
return HttpResponse('注册成功')
这样再次注册的时候,就能保证用户名的正确输入了
回到前文,怎样记住会话状态呢,在login_view()函数的get请求里面添加如下代码:
def login_view(request):
if request.method == 'GET':
# 检查登录状态,如果登录了,显示“已登录”
if request.session.get('username') and request.session.get(id):
return HttpResponse('已登录')
# 检查cookies
c_username = request.COOKIES.get('username')
c_id = request.COOKIES.get('id')
if c_id and c_username:
# 回写session
request.session['username'] = c_username
request.session['id'] = c_id
return HttpResponse('已登录')
return render(request, 'user/login.html')
从代码来看,先是判断session中是否存了usename和id,有的话显示已登录,没有的话检查cookies,也是检查是否存储了这两个值,如果都没有存储,就跳转到登录页面
至此,逻辑已经相对比较清楚
前面在登录的时候,get请求发现用户有登录的时候,直接HttpResponse(‘已登录’)显然是不合理的,正常是系统应该是登录后跳转到一个主页界面,现在就是要干这个事儿
创建应用index,注册应用,添加路由,这些操作不再记录
先写模板文件
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页title>
head>
<body>
{#{{ request.session }}#}
{#{{ request.COOKIES }}#}
{% if request.session.username %}
<p>
欢迎 {{ request.session.username }}
p>
<p>
<a href="">退出登录a>
p>
<p>
<a href="">进入我的笔记a>
p>
{% else %}
{% if request.COOKIES.username %}
<p>
欢迎 {{ request.COOKIES.username }}
p>
<p>
<a href="">退出登录a>
p>
<p>
<a href="">进入我的笔记a>
p>
{% else %}
<p>
<a href="/user/login">登录a>
p>
<p>
<a href="/user/reg">注册a>
p>
{% endif %}
{% endif %}
body>
html>
退出和进入笔记的超链接还没写
登录和注册都已经链接上,注意,登录或注册成功后,应该跳转到首页,同时,检查到cookies和session有过登录,也应该直接跳转的登录后的主页,因此,所有user/views.py中的HttpResponse('已登录')
和HttpResponse('登录成功')
都应该改成网页跳转,也就是重定向
return HttpResponseRedirect('/index')
给出的是相对地址
注意,还没写视图函数,这里的视图函数就非常简单了,如下
from django.shortcuts import render
# Create your views here.
def index_view(request):
return render(request, 'index/index.html')
直接渲染文件就行了
创建note应用,注册,添加路由
视图文件views.py中添加视图函数,首先检查是否是登录状态,后续使用装饰器来检查,节省代码
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render
from .models import *
from django.contrib import messages
# Create your views here.
def check_login(fn):
def wrap(request, *args, **kwargs):
if 'username' not in request.session or 'id' not in request.session:
c_username = request.COOKIES.get('username')
c_id = request.COOKIES.get('id')
if not c_username or not c_id:
return HttpResponseRedirect('/user/login')
else:
request.session['username'] = c_username
request.session['id'] = c_id
return fn(request, *args, **kwargs)
return wrap
仍然在视图文件中添加
@check_login
def add_note(request):
if request.method == 'GET':
return render(request, 'note/add_note.html')
if request.method == 'POST':
id = request.session['id']
title = request.POST['title']
content = request.POST['content']
Note.objects.create(title=title, content=content, user_id=id)
# return HttpResponse('添加笔记成功')
return HttpResponseRedirect('/note/note') # 重定向,跳转到图书列表
对应的模板文件add_note.html
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>添加笔记title>
<style>
p {
width: 150px;
}
style>
head>
<body>
<form action="/note/add" method="post">
<p>
标题:<input type="text" name="title">
p>
<p>
<textarea cols="30" rows="10" name="content">textarea>
p>
<p>
<input type="submit" value="保存" onclick="alert('保存成功')">
p>
form>
body>
html>
显示界面:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yi84cT29-1652317066153)(C:\Users\HP\AppData\Roaming\Typora\typora-user-images\image-20220507175730859.png)]
增加笔记视图函数中最后重定向到图书列表,因此要显示一个图书列表模板文件
list_note.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>笔记列表</title>
<style>
td, th {
border: 1px solid;
}
.note {border-collapse: collapse;}
</style>
</head>
<body>
<h3>
{{ request.session.username }} 的笔记 <a href="/note/add">添加新笔记</a> <a href="/index">返回首页</a>
</h3>
<table class="note">
<tr>
<th>ID</th>
<th>标题</th>
<th>创建时间</th>
<th>修改时间</th>
<th>修改</th>
<th>删除</th>
</tr>
{% for note in all_notes %}
<tr>
<td>{{ note.id }}</td>
<td>{{ note.title }}</td>
<td>{{ note.created_at }}</td>
<td>{{ note.updated_at }}</td>
<td><a href="/note/mod/{{ note.id }}">修改</a></td>
<td><a href="">删除</a></td>
</tr>
{% endfor %}
</table>
</body>
</html>
显示界面:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CipFue2A-1652317066154)(C:\Users\HP\Desktop\在办工作2021\99-其他临时工作\个人技术博客\image-20220507175800788.png)]
对应的视图函数
def list_view(request):
all_notes = Note.objects.all()
return render(request, 'note/list_note.html', locals())
先上模板文件mod.html
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>修改笔记title>
head>
<body>
<form action="/note/mod/{{ note.id }}" method="post">
<h3>
<p>主题:p>
<p>
<input type="text" name="title" value="{{ note.title }}">
p>
h3>
<h3>
<p>笔记详情p>
<p>
<textarea cols="30" rows="10" name="content" value="{{ note.content }}">{{ note.content }}textarea>
p>
h3>
<p>
<input type="submit" value="保存" onclick="alert('保存成功')">
<a href="/note/note">返回列表页a>
p>
form>
body>
html>
显示界面:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o7oHCNQV-1652317066154)(C:\Users\HP\AppData\Roaming\Typora\typora-user-images\image-20220507180334442.png)]
视图函数:
def mod_view(request, id):
try:
note = Note.objects.get(id=id)
except Exception as e:
print("修改出现错误:%s" % e)
return HttpResponse('笔记不存在')
if request.method == 'GET':
return render(request, 'note/mod_note.html', locals())
if request.method == 'POST':
title = request.POST['title']
content = request.POST['content']
note.title = title
note.content = content
note.save()
return HttpResponseRedirect('/note/note')
注意,这里是有参传入,传入了一个参数id,其实就是书本的id,因此,路由文件中可以使用path转换器来批量添加路由,如下:
note\urls.py
from django.urls import path
from note import views
urlpatterns = [
path('add', views.add_note),
path('note', views.list_view),
path('mod/' , views.mod_view),
]
这里个人调试了好久,发现有几个易错点:
return render(request, 'note/mod_note.html', locals())
中的地址最前面是没有斜杆/的,而模板文件中传递的相对路由
最前面则是有斜杆的127.0.0.1:8000/note/note
,这和课件是有出入的,而模板文件中,如第一个易错点的form标签中的路由地址,这应该是添加在127.0.0.1:8000之后的地址,这点比较重要,理解清楚了,路由就不会再犯错了。删除功能的增加相对简单,删除不是真删除,要伪删除,也就是在模型中添加伪删除字段,is_active,默认值是true,笔记列表中仅显示is_active=True的笔记,然后增加删除的视图函数del_view(),将点击删除的笔记的is_active值设置为false,保存后重定向值note列表,具体流程如下:
models.py中添加下面一行代码
is_active = models.BooleanField('是否显示', default=True)
数据迁移
增加视图函数del_view(),如下
def del_view(request, id):
try:
note = Note.objects.get(id=id)
except Exception as e:
print("删除出现错误:%s" % e)
return HttpResponse('笔记不存在')
note.is_active = False
note.save()
return HttpResponseRedirect('/note/note')
分布式路由中添加删除操作的路由,并绑定视图函数
模板文件(list_note.html)中添加删除的超链接,如下
<td><a href="/note/del/{{ note.id }}" onclick="return confirm('是否确认删除');">删除a>td>
注意,之前显示笔记列表的视图函数查找数据用的是all_notes = Note.objects.all()
,这查的是所有的数据,显然不合理,删除后应该不显示伪删除的数据,所以应该改成all_notes = Note.objects.filter(is_active=True)
,而且这里的变量名all_notes不能瞎改,因为传入到模板文件list_note.html中的数据就是all_notes
至此,完成云笔记项目的所有功能,后面我会把整个项目放到git上去
通过这个非常简单的项目,大体上是捋清了使用Django框架进行前后端操作的步骤,总体上应把握以下几点
更快的读取数据的介质,存储临时数据
一般情况下,当数据库中有大量的数据时,使用orm查询数据会非常慢,因此如果缓存中有数据的话,直接从缓存中取出数据,会简单很多
Django官方给的思路:
缓存的使用场景:数据变动频率较低
目前,个人科研工作中的项目开发,应该暂时不会去设置缓存,因此不做具体的项目,只做了解
数据库缓存
把一次负责查询的结果直接存储到表里,比如多个条件的过滤查询结果,可以避免重复进行复杂的查询,以提升效率
配置方法,在settings.py中设置CACHES字段
可以存储在数据库中,也可以存在服务器本地内存中,文件系统中
数据库缓存示例:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GQL23Kp7-1652317066155)(C:\Users\HP\AppData\Roaming\Typora\typora-user-images\image-20220508141055835.png)]
服务器缓存:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3pukUlxj-1652317066155)(C:\Users\HP\AppData\Roaming\Typora\typora-user-images\image-20220508141123707.png)]
文件系统缓存
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9C3as1Ae-1652317066155)(C:\Users\HP\AppData\Roaming\Typora\typora-user-images\image-20220508141259518.png)]
视图函数中的相应内容放到缓存中,使用装饰器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AZ9rMcET-1652317066159)(C:\Users\HP\AppData\Roaming\Typora\typora-user-images\image-20220508142149690.png)]
在路由中套上装饰器,与1是一回事
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bs7VV787-1652317066160)(C:\Users\HP\AppData\Roaming\Typora\typora-user-images\image-20220508142221486.png)]
整体缓存无法干预,没法删除,简单粗暴,无法认为控制
如果只想缓存视图函数中的某行代码,就可以用到局部缓存
缓存API
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZyS3SlNx-1652317066160)(C:\Users\HP\AppData\Roaming\Typora\typora-user-images\image-20220508143303391.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5oZmz96c-1652317066160)(C:\Users\HP\AppData\Roaming\Typora\typora-user-images\image-20220508143816396.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pP8Us2ez-1652317066161)(C:\Users\HP\AppData\Roaming\Typora\typora-user-images\image-20220508143828878.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a0sEzvkr-1652317066161)(C:\Users\HP\AppData\Roaming\Typora\typora-user-images\image-20220508144052223.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RlXogKB2-1652317066161)(C:\Users\HP\AppData\Roaming\Typora\typora-user-images\image-20220508144124003.png)]
浏览器是否缓存,需要服务器给暗号
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5Gi8M5h8-1652317066162)(C:\Users\HP\AppData\Roaming\Typora\typora-user-images\image-20220508144952534.png)]
上面这连个参数,都可以通过整体缓存策略中的装饰器传给浏览器,不需要单独配置
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5GNDsjy7-1652317066162)(C:\Users\HP\AppData\Roaming\Typora\typora-user-images\image-20220508145353529.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FjeQrjpR-1652317066162)(C:\Users\HP\AppData\Roaming\Typora\typora-user-images\image-20220508145527258.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VwqquOzd-1652317066163)(C:\Users\HP\AppData\Roaming\Typora\typora-user-images\image-20220508145737037.png)]
用于请求、响应,用于全局改变Django的输入输出
中间件以类的形式体现
每个中间件组件负责一些特定的功能
中间件必须继承自django.utils.desprecation.MiddlewareMixin类
必须实现以下五个方法中的一个或多个(主要前三个):
process_request(self, request)——起拦截作用
执行路由之前被调用,在每个请求上调用,返回none或HttpResponse对象
process_view(self, request, callback_args, callback_kwargs)——拦截
调用视图之前被调用,在每个请求上调用,返回none或HttpResponse对象
process_response(self, request, response)
响应返回给浏览器之前被调用,返回HttpResponse对象
process_exception(self, request, exception)
处理异常时调用,返回HttpResponse对象,可能项目会报错,比如给用户发个邮件,告诉用户有错误
process_template_response(self, request, response)——很少用到
在视图函数执行完毕且视图返回的对象中包含render方法时被调用,需要返回实现了render方法的响应对象
中间件的大多数方法在返回None时表示忽略当前操作进入下一项事件,当返回HttpResponse对象时表示此请求有问题,要结束,直接返回给客户端
注册方法:
settings.py中注册自定义的中间件
在项目目录下创建一个文件夹middleware(严谨来说,是创建一个Python包),接着去创建一个__init__.py
初始化文件和一个中间件Python文件(如mymiddleware.py),在mymiddleware.py中添加类,继承自,,,
写好之后去注册
# file : middleware/mymiddleware.py
from django.http import HttpResponse, Http404
from django.utils.deprecation import MiddlewareMixin
class MyMiddleWare(MiddlewareMixin):
def process_request(self, request):
print("中间件方法 process_request 被调用")
def process_view(self, request, callback, callback_args, callback_kwargs):
print("中间件方法 process_view 被调用")
def process_response(self, request, response):
print("中间件方法 process_response 被调用")
return response
def process_exception(self, request, exception):
print("中间件方法 process_exception 被调用")
def process_template_response(self, request, response):
print("中间件方法 process_template_response 被调用")
return response
# file : settings.py
MIDDLEWARE = [
...
'middleware.mymiddleware.MyMiddleWare',
]
#单个中间件输出
MyMW process_request do---
MyMW process_views do ---
----this is test cache views ----
MyMW process_response do ---
#多个中间件时 输出
MyMW process_request do---
MyMW2 process_request do---
MyMW process_views do ---
MyMW2 process_views do ---
----this is test cache views ----
MyMW2 process_response do ---
MyMW process_response do ---
用中间件实现强制某个IP地址只能向/test 发送 5 次GET请求
提示:
答案:
from django.http import HttpResponse, Http404
from django.utils.deprecation import MiddlewareMixin
import re
class VisitLimit(MiddlewareMixin):
'''此中间件限制一个IP地址对应的访问/user/login 的次数不能改过10次,超过后禁止使用'''
visit_times = {} # 此字典用于记录客户端IP地址有访问次数
def process_request(self, request):
ip_address = request.META['REMOTE_ADDR'] # 得到IP地址
if not re.match('^/test', request.path_info):
return
times = self.visit_times.get(ip_address, 0)
print("IP:", ip_address, '已经访问过', times, '次!:', request.path_info)
self.visit_times[ip_address] = times + 1
if times < 5:
return
return HttpResponse('你已经访问过' + str(times) + '次,您被禁止了')
跨站请求伪造攻击
说明:
CSRF中间件和模板标签提供对跨站请求伪造简单易用的防护。
作用:
解决方案:
取消 csrf 验证(不推荐)
django.middleware.csrf.CsrfViewMiddleware
的中间件通过验证 csrf_token 验证
需要在表单中增加一个标签
{% csrf_token %}
分页是指在web页面有大量数据需要显示,为了阅读方便在每个页页中只显示部分数据
django.core.paginator
模块中。掌控全部页面
对象的构造方法
Paginator属性
Paginator方法
Paginator异常exception
掌控单个页面
创建对象
Paginator对象的page()方法返回Page对象,不需要手动构造
Page对象属性
Page对象方法
说明:
参考文档https://docs.djangoproject.com/en/1.11/topics/pagination/
示例:
视图函数
from django.core.paginator import Paginator
from django.shortcuts import render
def test_page(request):
# /test_page?page=1 使用查询字符串的方式
page_num = request.GET.get('page', 1)
all_data = ['a', 'b', 'c', 'd', 'e']
paginator = Paginator(all_data, 2)
c_page = paginator.page(int(page_num))
return render(request, 'test_page.html', locals())
模板文件test_page.html
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>分页title>
head>
<body>
{% for p in c_page %}
<p>
{{ p }}
p>
{% endfor %}
{% if c_page.has_previous %}
<a href="/test_page?page={{ c_page.previous_page_number }}">上一页a>
{% else %}
上一页
{% endif %}
{% for p_num in paginator.page_range %}
{% if p_num == c_page.number %}
{{ p_num }}
{% else %}
<a href="/test_page?page={{ p_num }}">{{ p_num }}a>
{% endif %}
{% endfor %}
{% if c_page.has_next %}
<a href="/test_page?page={{ c_page.next_page_number }}">下一页a>
{% else %}
下一页
{% endif %}
body>
html>
路由
from django.contrib import admin
from django.urls import path, include
from cloud_note import views
urlpatterns = [
path('admin/', admin.site.urls),
path('user/', include('user.urls')),
path('index/', include('index.urls')),
path('note/', include('note.urls')),
path('test_page', views.test_page),
]
视图函数
def make_page_csv(request):
page_num = request.GET.get('page', 1)
all_data = ['a', 'b', 'c', 'd', 'e']
paginator = Paginator(all_data, 2)
c_page = paginator.page(int(page_num))
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment;filename="page-%s.csv"' % (page_num)
print(response)
writer = csv.writer(response)
for b in c_page:
writer.writerow([b])
return response
模板文件
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>分页title>
head>
<body>
<a href="/make_page_csv?page={{ c_page.number }}">生成当前页面csva>
{% for p in c_page %}
<p>
{{ p }}
p>
{% endfor %}
{% if c_page.has_previous %}
<a href="/test_page?page={{ c_page.previous_page_number }}">上一页a>
{% else %}
上一页
{% endif %}
{% for p_num in paginator.page_range %}
{% if p_num == c_page.number %}
{{ p_num }}
{% else %}
<a href="/test_page?page={{ p_num }}">{{ p_num }}a>
{% endif %}
{% endfor %}
{% if c_page.has_next %}
<a href="/test_page?page={{ c_page.next_page_number }}">下一页a>
{% else %}
下一页
{% endif %}
body>
html>
修改路由
关键逻辑在于下面这两行代码
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment;filename="page-%s.csv"' % (page_num)
文件上传必须为POST提交方式
表单中文件上传时必须有带有
enctype="multipart/form-data"
时才会包含文件内容数据。
表单中用标签上传文件
xxx
对应request.FILES['xxx']
对应的内存缓冲文件流对象。可通能过request.FILES['xxx']
返回的对象获取上传文件数据file=request.FILES['xxx']
file 绑定文件流对象,可以通过文件流对象的如下信息获取文件数据上传文件的表单书写方式
<html>
<head>
<meta charset="utf-8">
<title>文件上传title>
head>
<body>
<h3>上传文件h3>
<form method="post" action="/upload" enctype="multipart/form-data">
<input type="file" name="myfile"/><br>
<input type="submit" value="上传">
form>
body>
html>
在setting.py 中设置一个变量MEDIA_ROOT 用来记录上传文件的位置
# file : settings.py
...
MEDIA_ROOT = os.path.join(BASE_DIR, 'static/files')
在当前项目文件夹下创建 static/files
文件夹
$ mkdir -p static/files
添加路由及对应的处理函数
# file urls.py
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^upload', views.upload_view)
]
配置media后,需要手动添加路由,添加方式比较特殊
# file urls.py
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import path
from mysite08 import views
urlpatterns = [
path('admin/', admin.site.urls),
path('test_upload', views.test_upload),
]
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
这个过程等价于做了MEDIA_URL开头的路由,Django接到该请求后去MEDIA_ROOT路径查找资源
上传文件的视图处理函数–第一种方法,传统的open写入,但会存在重名文件覆盖的问题
# file views.py
from django.http import HttpResponse, Http404
from django.conf import settings
import os
def upload_view(request):
if request.method == 'GET':
return render(request, 'index/upload.html')
elif request.method == "POST":
a_file = request.FILES['myfile']
print("上传文件名是:", a_file.name)
filename =os.path.join(settings.MEDIA_ROOT, a_file.name)
with open(filename, 'wb') as f:
data = a_file.file.read()
f.write(data)
return HttpResponse("接收文件:" + a_file.name + "成功")
raise Http404
访问地址: http://127.0.0.1:8000/static/upload.html
上传文件的视图处理函数–第二种方法,Django封装好的方法
数据库中添加一张表,用于存放上传数据的名称和文件名,步骤
创建应用---->添加模型类---->添加模型---->模型迁移---->视图函数
模型类(upload_app/models.py)
from django.db import models
# Create your models here.
class Content(models.Model):
title = models.CharField('文章名字', max_length=200)
picture = models.FileField(upload_to='picture')
视图函数(views.py)
#!/usr/bin/env python
#-*- coding:utf-8 -*-
# author:HP
# datetime:2022/5/11 10:45
from django.http import HttpResponse
from django.shortcuts import render
# from upload_app import models
from upload_app.models import Content
def test_upload(request):
if request.method == 'GET':
return render(request, 'test_upload.html')
elif request.method == 'POST':
title = request.POST['title']
myfile = request.FILES['myfile']
Content.objects.create(title=title, picture=myfile)
return HttpResponse('---文件上传成功')
后端缓存:
1, 将视图函数最终结果 转存到其他介质里
【mysql表里,文件里, 内存里】
2,解决了 views重复计算问题 【有效降低视图层时间复杂度】
3,http 1.1 cache头 触发了 浏览器强缓存
浏览器缓存:
1,带有强缓存的响应头 的响应数据,存储自己的硬盘中或内存里
2,当强缓存有数据时,可以完全不给服务器发送请求,直接读取缓存内容 【减少 浏览器与服务器之间的请求次数】
这里的用户内建系统和云笔记项目中的user应用作用一样
Django带有一个用户认证系统。 它处理用户账号、组、权限以及基于cookie的用户会话。
作用:
文档参见
https://docs.djangoproject.com/en/1.11/topics/auth/
User模型类
位置: from django.contrib.auth.models import User
默认user的基本属性有:
属性名 | 类型 | 是否必选 |
---|---|---|
username | 用户名 | 是 |
password | 密码 | 是 |
邮箱 | 可选 | |
first_name | 名 | |
last_name | 姓 | |
is_superuser | 是否是管理员帐号(/admin) | |
is_staff | 是否可以访问admin管理界面 | |
is_active | 是否是活跃用户,默认True。一般不删除用户,而是将用户的is_active设为False。 | |
last_login | 上一次的登录时间 | |
date_joined | 用户创建的时间 |
创建用户
创建普通用户create_user
from django.contrib.auth import models
user = models.User.objects.create_user(username='用户名', password='密码', email='邮箱',...)
...
user.save()
创建超级用户create_superuser
from django.contrib.auth import models
user = models.User.objects.create_superuser(username='用户名', password='密码', email='邮箱',...)
...
user.save()
删除用户
from django.contrib.auth import models
try:
user = models.User.objects.get(username='用户名')
user.is_active = False # 记当前用户无效
user.save()
print("删除普通用户成功!")
except:
print("删除普通用户失败")
return HttpResponseRedirect('/user/info')
修改密码set_password
from django.contrib.auth import models
try:
user = models.User.objects.get(username='xiaonao')
user.set_password('654321')
user.save()
return HttpResponse("修改密码成功!")
except:
return HttpResponse("修改密码失败!")
检查密码是否正确check_password
from django.contrib.auth import models
try:
user = models.User.objects.get(username='xiaonao')
if user.check_password('654321'): # 成功返回True,失败返回False
return HttpResponse("密码正确")
else:
return HttpResponse("密码错误")
except:
return HttpResponse("没有此用户!")
登录状态保持
from django.contrib.auth import login
def login_view(request):
user = authenticate(username=username, password=password)
login(request, user)
登录状态校验
需要使用装饰器login_required
# 伪代码
from django.contrib.auth import login_required
@login_required
def index_view(request):
# 该视图必须为用户登录状态下才可访问
# 当前登录用户可以通过request.user获取
login_user = request.user
...
登录状态取消
更简单,直接使用内置的logout方法
from django.contrib.auth import logout
def logout_view(request):
logout(request)
业务场景:
业务警告
邮件验证
密码找回
SMTP 简单邮件传输协议 发邮件 推送协议
IMAP 交互式邮件访问协议 收邮件 拉取协议
POP3 支持使用客户端远程管理在服务器上的电子邮件 拉取协议
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Tyr8LZZH-1652317066163)(C:\Users\HP\AppData\Roaming\Typora\typora-user-images\image-20220511135259744.png)]
Django充当邮件客户端的身份,自动发出邮件
原理:
settings.py中配置邮件发送
# 发送邮件设置
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' # 固定写法
EMAIL_HOST = 'smtp.qq.com' # 腾讯QQ邮箱 SMTP 服务器地址
EMAIL_PORT = 25 # SMTP服务的端口号
EMAIL_HOST_USER = '[email protected]' # 发送邮件的QQ邮箱
EMAIL_HOST_PASSWORD = '******' # 邮箱的授权码(即QQ密码)
EMAIL_USE_TLS = True # 与SMTP服务器通信时,是否启动TLS链接(安全链接)默认false
这一段根据自己的实际情况添加
settings.py中自定义要发送邮件的对象
EX_EMAIL = ['马赛克[email protected]', '马赛克[email protected]']
添加中间件,报错时发送邮件
from django.http import HttpResponse, Http404
from django.utils.deprecation import MiddlewareMixin
import traceback
from django.core import mail
from django.conf import settings
class ExceptionMW(MiddlewareMixin):
def process_exception(self, request, exception):
print(exception)
print(traceback.format_exc())
mail.send_mail(
subject='来自Django的项目告警信息',
message=traceback.format_exc(),
from_email='马赛克[email protected]', # 发送者[当前配置邮箱]
recipient_list=settings.EX_EMAIL
)
return HttpResponse('---对不起 当前网页忙')
有几点要注意,中间件写好后,要到settings.py中去注册,另外,邮件的接受列表直接调用自第2步中添加的邮件列表
发送邮件结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-44s6QHJE-1652317066164)(C:\Users\HP\AppData\Roaming\Typora\typora-user-images\image-20220511152306208.png)]
部署过程中遇到的问题先记录
局域网其他设备无法访问的问题
解决方法:
settings.py中修改ALLOWED_HOSTS = [‘*’]
pycharm终端启动Django服务命令python manage.py runsever 0.0.0.0:8000
, 0.0.0.0代表网络内所有的主机都可以访问
其他设备访问192.168.43.246:8000/index
即可,192.168.43.246为主机(服务器)的ip
局域网内手机访问云笔记项目
django项目上线