Django开发现成的数据库
# 把结果在根目录中生成'models.py'
1.前期准备工作先做好,然后使用命令(注,终端不能使用pycharm的'tool')---python manage.py inspectdb > models.py
然后到根目录中查找这个文件'models.py'
models.py
This is an auto-generated Django model module.
You'll have to do the following manually to clean this up:
* Rearrange models' order
* Make sure each model has one field with primary_key=True
* Make sure each ForeignKey has on_delete
set to the desired behavior.
* Remove managed = False
lines if you wish to allow Django to create, modify, and delete the table
Feel free to rename the models, but don't rename db_table values or field names.
from django.db import models
class Article(models.Model):
title = models.CharField(max_length=100, blank=True, null=True)
content = models.TextField(blank=True, null=True)
# author = models.ForeignKey('User', models.SET_NULL, blank=True, null=True)
author = models.ForeignKey('User', models.DO_NOTHING, blank=True, null=True)
# 注意db_table这个参数,值要和数据库的'article_tab'表对应
# tags=models.ManyToManyField('Tag',db_table='article_tag') # 多对多关系,在中间表中体现,原始表是没有这个字段的
class Meta:
managed = False
db_table = 'article'
ArticleTag模型要全部注释掉,因为不需要这张表
class ArticleTag(models.Model):
article = models.ForeignKey(Article, models.DO_NOTHING, primary_key=True)
tag = models.ForeignKey('Tag', models.DO_NOTHING)
class Meta:
managed = False
db_table = 'article_tag'
unique_together = (('article', 'tag'),) ## 这句话的含义???
class Tag(models.Model):
name = models.CharField(max_length=100, blank=True, null=True)
class Meta:
managed = False
db_table = 'tag'
class User(models.Model):
username = models.CharField(max_length=100, blank=True, null=True)
password = models.CharField(max_length=100, blank=True, null=True)
class Meta:
managed = False
db_table = 'user'
创建两个app,front存放User模型,article存放其他模型
front.models
from django.db import models
class User(models.Model):
username = models.CharField(max_length=100, blank=True, null=True)
password = models.CharField(max_length=100, blank=True, null=True)
class Meta:
db_table = 'user'
article.models
from django.db import models
class ArticleTag(models.Model):
article = models.ForeignKey('Article', models.DO_NOTHING, primary_key=True)
tag = models.ForeignKey('Tag', models.DO_NOTHING)
class Meta:
db_table = 'article_tag'
unique_together = (('article', 'tag'),) ## 这句话的含义???
class Article(models.Model):
title = models.CharField(max_length=100, blank=True, null=True)
content = models.TextField(blank=True, null=True)
author = models.ForeignKey('front.User', models.SET_NULL, blank=True, null=True)
tags=models.ManyToManyField('Tag',db_table='article_tag')
class Meta:
db_table = 'article'
class Tag(models.Model):
name = models.CharField(max_length=100, blank=True, null=True)
class Meta:
db_table = 'tag'
把模型更正完成后,执行 python manage.py makemigrations
然后执行: python manage.py migrate front(/article) --fake-initial
最后,测试django运行是否正常
article.views
from django.shortcuts import render
from django.http import HttpResponse
from .models import *
from front.models import User
def index(request):
# article=Article(title='Learn to live',content='A book about living...')
# author=User.objects.create(username='Jim Green',password='123')
# author.save() # 这步多余
# article.author=author
# article.save()
article=Article.objects.first()
tag1=Tag.objects.create(name='Nature')
tag2=Tag.objects.create(name='Animal')
article.tags.add(tag1,tag2)
article.save()
return HttpResponse('index')
结果表明,都符合预期
数据库番外篇
● 定义性别字段的方法,示例:
class HeroInfo(models.Model):
GENDER_CHOICES=(
(0,'male'), # 0代表男性,1代表女性
(1,'female')
)
hname=models.CharField(max_length=20,verbose_name='名称')
# 这里把性别定义为'小型整型'
hgender=models.SmallIntegerField(choices=GENDER_CHOICES,default=0,verbose_name='性别')
......
● 增加字段的方法---save();object.create()
● 查询方法---get(),filter(),exclude()
● 属性与常量值的比较,例如objects.filter(id=3),objects.filter(create_time__gte=2019)
上述示例仔细小型,就是'属性与常量值'的比较,那么,如果是'属性与属性'的比较---F表达式
示例:
class BookInfo(models.Model):
......
bread=models.IntegerField(default=0,verbose_name='阅读量')# 两个类型相同的字段
bcomment=models.IntegerField(default=0,verbose_name='评论量')
......
注:F表达式是可以运算的,比如这句还可以改为:filter(bread__gte=F('bcomment')*2
BookInfo.objects.filter(bread__gte=F('bcomment')
● Q表达式的再次分析:
• Q对象---多个过滤器逐个调用表示逻辑与关系,同sql语句中where部分的and关键字。
例:查询阅读量大于20,并且编号小于3的图书。
BookInfo.objects.filter(bread__gt=20,id__lt=3)
或 # 实践表明,这两句是一样的效果
BookInfo.objects.filter(bread__gt=20).filter(id__lt=3)
• 如果需要实现逻辑或or的查询,需要使用Q()对象结合|运算符,Q对象被义在django.db.models中。
语法如下:
Q(属性名__运算符=值)
示例例:查询阅读量大于20的图书,改写为Q对象如下。
from django.db.models import Q
BookInfo.objects.filter(Q(bread__gt=20)) # BookInfo.objects.filter(bread__gt=20)
• Q对象可以使用&、|连接,&表示逻辑与,|表示逻辑或。
例:查询阅读量大于20,或编号小于3的图书,只能使用Q对象实现
BookInfo.objects.filter(Q(bread__gt=20) | Q(pk__lt=3))
1
Q对象前可以使用~操作符,表示非not。
例:查询编号不等于3的图书。
BookInfo.objects.filter(~Q(pk=3))
标记url---https://blog.csdn.net/yanpenggong/article/details/82316514
● 关联查询---原始表和外键表的相互查询
models
from django.db import models
class BookInfo(models.Model): # 外键表
btitle=models.CharField(max_length=20,verbose_name='名称')
bpub_date=models.DateField(verbose_name='发布日期')
bread=models.IntegerField(default=0,verbose_name='阅读量')
bcomment=models.IntegerField(default=0,verbose_name='评论量')
is_delete=models.BooleanField(default=False,verbose_name='逻辑删除')
class Meta:
db_table='tb_books'
verbose_name='图书' # admin站点的名称
verbose_name_plural=verbose_name # 显示的复数名称
def __str__(self):
return self.btitle
class HeroInfo(models.Model): # 原始表
GENDER_CHOICES=(
(0,'male'),
(1,'female')
)
hname=models.CharField(max_length=20,verbose_name='名称')
hgender=models.SmallIntegerField(choices=GENDER_CHOICES,default=0,verbose_name='性别')
hcomment=models.CharField(max_length=200,null=True,verbose_name='描述信息')
hbook=models.ForeignKey(BookInfo,on_delete=models.CASCADE,verbose_name='图书')
is_delete=models.BooleanField(default=False,verbose_name='逻辑删除')
class Meta:
db_table='tb_heros'
verbose_name='英雄'
verbose_name_plural=verbose_name
def __str__(self):
return self.hname
• 由一到多的访问语法:
外键表--->原始表:模型类名小写_set 例:
b = BookInfo.objects.get(id=1) # 获取外键表第一条记录
这里的heroinfo_set可以当做objects对象处理,比如这里还可以使用first()方法
b.heroinfo_set.all() # _set的形式访问原始表该字段的所有记录
由多到一的访问语法:
原始表--->外键表:模型类中的关系类属性名 例:
h = HeroInfo.objects.get(id=1) # 获取原始表第一条记录
h.hbook # 直接通过属性的形式访问
● 关联过滤查询
外键表--->原始表:模型名小写属性[运算符]=xxx
示例:
books=Book.objects.filter(bookorder__price__lt=100)# 外键表--->原始表
for book in books:
print(book.name)
示例2:
books=Book.objects.filter(bookorder__create_time__year=2017)
for book in books:
print(book.name)
return HttpResponse('index20')
原始表--->外键表 也是一样的语法
● 字段值的更新
<1> 修改模型类对象的属性,然后执行save()方法,示例:
book=Book.objects.first()
book.name='自然传说'
book.save()
<2> update(),示例:
# 注意,update()有批量更新的效果,如果查询的结果有多个,那么就是批量更新...
book=Book.objects.filter(name__icontains='常识').update(name='水果蔬菜养生')
return HttpResponse('index20')
● 删除
删除有两种方法
<1>模型类对象delete
hero = HeroInfo.objects.get(id=13)
hero.delete()
<2>QuerySet对象:filter().delete()
HeroInfo.objects.filter(id=14).delete()
● QuerySet---查询集:从数据库中获取的对象的集合
当调用如下过滤器方法时,Django会返回查询集(而不是简单的列表):
<1>all():返回所有数据。
<2>filter():返回满足条件的数据。
<3>exclude():返回满足条件之外的数据。
<4>order_by():对结果进行排序。
对查询集可以再次调用过滤器进行过滤,比如连续调用filter()方法
● 缓存
使用同一个查询集,第一次使用时会发生数据库的查询,然后Django会把结果缓存下来,再次使用这个查询集时会使用缓存的数据,减少了数据库的查询次数。
实例1:如下是两个查询集,无法重用缓存,每次查询都会与数据库进行一次交互,增加了数据库的负载。
from booktest.models import BookInfo
[book.id for book in BookInfo.objects.all()] # 不会使用'缓存',增加开销
[book.id for book in BookInfo.objects.all()]
实例2:经过存储后,可以重用查询集,第二次使用缓存中的数据。
qs=BookInfo.objects.all()
[book.id for book in qs]
[book.id for book in qs]# 二次遍历就会使用缓存
● 管理器Manager
管理器是Django的模型进行数据库操作的'接口';Django应用的每个模型类都拥有至少一个管理器。
比如objects,实质就是django提供的默认管理器,它是models.Manager类的对象。
高级视图
1.get/post方法:
<1> get---向服务端'索取'数据,不会更改服务器的状态
<2> post---向服务端'提交'数据,会更改服务器的状态
新项目示例:
article.models
from django.db import models
class Article(models.Model):
title=models.CharField(max_length=100)
content=models.TextField()
price=models.FloatField(default=0)
create_time=models.DateTimeField(auto_now_add=True)
class Meta:
db_table='article'
article.views
from django.shortcuts import render
from .models import Article
def index(request):
pass
def add_article(request):
pass
urls
urlpatterns = [
path('', views.index),
path('add_article/', views.add_article,name='add_article'),
]
settings的配置就不写了
现在获取article所有的记录,看看method
views
from django.shortcuts import render
from .models import Article
def index(request):
articles=Article.objects.get_queryset() # 获取所有的QuerySet对象
context={
'articles':articles
}
return render(request,'index.html',context=context)
index.html
- {{ article.title }}
{% for article in articles %} # 遍历获取所有的记录
{% endfor %}
使用谷歌浏览器查看结果,明显就是'get'方法
但是,其实是有缺陷的,比如,使用postman测试工具的时候
使用'post'方法,虽然不规范,但是也能获取一样的结果,怎么解决这个问题---使用django提供装饰器
views
from django.views.decorators.http import require_http_methods # 导入装饰器
@require_http_methods(['GET']) # 接收的参数是list,get方法使用大写
def index(request):
articles=Article.objects.get_queryset()
context={
'articles':articles
}
return render(request,'index.html',context=context)
此时再使用postman测试工具,get方法去请求url,OK;但是使用post方法去请求url,就失败了
这正是装饰器的作用.
上述views还可以这么写:
from django.shortcuts import render
from .models import Article
from django.views.decorators.http import require_http_methods,require_GET # 导入装饰器
@require_http_methods(['GET']) # 注释掉
这里如果想同时允许get/post就这么处理
@require_http_methods(['GET','POST'])
@require_GET # 用这个装饰器,效果等于上面那句
def index(request):
articles=Article.objects.get_queryset()
context={
'articles':articles
}
return render(request,'index.html',context=context)
现在测试'POST'方法:
views
def add_article(request):
title=request.POST.get('title') # 获取postman提交的内容
content=request.POST.get('content')
Article.objects.create(title=title,content=content) # 存入数据库
return HttpResponse('Successful!')
urls
urlpatterns = [
path('',views.index),
path('add_article',views.add_article,name='add_article') # 调用的url
]
postman输入相应的key,value,例如
http://127.0.0.1:8000/add_article?title=十万个为什么&content=关于疑问的书本
使用装饰器效果:
@require_POST # 限制只能使用post方法
def add_article(request):
title=request.POST.get('title')
content=request.POST.get('content')
Article.objects.create(title=title,content=content)
return HttpResponse('Successful!')
实际开发中,请求一个url,可以既使用get,又使用post,示例:
views
@require_http_methods(['POST','GET'])
def add_article(request):
# 如果使用GET请求来访问这个视图函数,那么就返回一个添加文章的HTML页面
# 如果使用POST请求来访问这个视图函数,那么就获取提交上来的数据,然后保存
# 到数据库中
if request.method == 'GET':
return render(request,'add_article.html')
else:
title = request.POST.get("title")
content = request.POST.get('content')
Article.objects.create(title=title,content=content)
return HttpResponse('success')
add_article.html
第一次访问url,那么就是get请求,加载这个前端网页,而提交数据后,就是使用post方法
把数据插入到数据库中
● 重定向---redirect
重定向分为永久性重定向和暂时性重定向,在页面上体现的操作就是浏览器会从一个页面自动跳转到另外一个页面。比如用户访问了一个需要权限的页面,但是该用户当前并没有登录,因此我们应该给他重定向到登录页面。
• 永久性重定向:http的状态码是301,多用于旧网址被废弃了要转到一个新的网址确保用户的访问,最经典的就是京东网站,你输入www.jingdong.com的时候,会被重定向到www.jd.com,因为jingdong.com这个网址已经被废弃了,被改成jd.com,所以这种情况下应该用永久重定向。
• 暂时性重定向:http的状态码是302,表示页面的暂时性跳转。比如访问一个需要权限的网址,如果当前用户没有登录,应该重定向到登录页面,这种情况下,应该用暂时性重定向。
在Django中,重定向是使用redirect(to, *args, permanent=False, **kwargs)来实现的。to是一个url,permanent代表的是这个重定向是否是一个永久的重定向,默认是False。关于重定向的使用。请看以下例子:
front.views
from django.shortcuts import render,redirect,reverse
from django.http import HttpResponse
def index(request):
username=request.GET.get('username')
if username:
return HttpResponse('index')
else:
return redirect(reverse('register'))
def register(request):
return HttpResponse('register')
● request对象常用的属性和方法
首先这个request对象并不是浏览器传给我们的,而是django接受浏览器传过来所有信息再次'封装'的
request实质是'WSGIRequest'类,示例:
from django.shortcuts import render
from django.http import HttpResponse
from django.core.handlers.wsgi import WSGIRequest # 有兴趣可以看看WSGIRequest源码
def index(request):
print(type(request)) #
return HttpResponse('index')
<1>request.path---返回url的完整路径(不包含协议和域名)
例如:http://www.baidu.com/xxx/yyy/,那么path就是/xxx/yyy/
def register(request):
print(request.path) # /register
return HttpResponse('register')
main_url.py
from django.contrib import admin
from django.urls import path
from front import views
urlpatterns = [
path('admin/', admin.site.urls),
path('', views.index),
path('login/', views.login), # url虽然是这么定义的,但是其实是可以接收查询字符串参数格式的
]
现在若是输入'查询字符串'的url,此时若还是使用request.path,那么,url后面的'查询字符串'是不会被打印的
解决办法---request.get_full_path()
def login(request):
print(request.path) # /login/
print(''30)
print(request.get_full_path()) # /login/?username=jimgreen
return HttpResponse('login')
若想获取全部的url:即协议+域名+路径---get_raw_uri(),示例:
def login(request):
print(request.get_raw_uri()) # http://127.0.0.1:8000/login/
return HttpResponse('login')
若单单只想获取域名,可以使用---request.get_host()
<2>request.method---返回请求的方法GET或POST
<3>request.GET/POSt
GET:一个django.http.request.QueryDict对象。操作起来类似于字典。这个属性中包含了所有以?xxx=xxx的方式上传上来的参数。
POST:也是一个django.http.request.QueryDict对象。这个属性中包含了所有以POST方式上传上来的参数。
示例:
def index(request):
username=request.GET.get('username')
print(request.GET)#
if username:
return HttpResponse('index')
else:
return redirect(reverse('register'))
<4>COOKIES
COOKIES:一个标准的Python字典,包含所有的cookie,键值对都是字符串类型。
示例:
def index(request):
username=request.GET.get('username')
print(request.COOKIES) # {'csrftoken': 'BaYkuGhZwQb2gu41lUHxzy17WT4EUWdGCNbE1YzmPHWegZAMJjDOkvLB6BXwUS49'}
if username:
return HttpResponse('index')
else:
return redirect(reverse('register'))
<5>META---包含浏览器(客户端)传过来的'header'信息,纯python字典,但是它还具有python字典所有没有的方法
比如---has_key()
META:存储的客户端发送上来的所有header信息。返回一个python字典,示例:
def login(request):
for k,v in request.META.items():
print(k,v) # 字典信息多,这里就不打印出来了...
return HttpResponse('login')
META字典有个值得关注的'key'---REMOTE_ADDR:客户端的IP地址,可以这么获取:
如果服务器使用了nginx做反向代理或者负载均衡,那么这个值返回的是'127.0.0.1'
这时候可以使用'HTTP_X_FORWARDED_FOR'来获取,所以获取ip地址的代码片段如下:
if request.META.has_key('HTTP_X_FORWARDED_FOR'):
ip = request.META['HTTP_X_FORWARDED_FOR']
else:
ip = request.META['REMOTE_ADDR']
<6> 如果判断ajax请求
拉勾网的登陆界面---从'network'切换到'XHR'---'Header'---'Request Header'---'X-Requested-With: XMLHttpRequest'
现在使用'postman'来模拟这个'ajax'请求一个url
http://127.0.0.1:8000/login/ ## 最末尾的'/'一定要加进去,否则就是不通....一定要记住...
key:X-Requested-With
value:XMLHttpReques
发送,发现url变成'查询字符串'的样式:http://127.0.0.1:8000/login/?X-Requested-With=XMLHttpRequest
● QueryDict对象
我们平时用的request.GET和request.POST都是QueryDict对象,这个对象继承自dict,因此用法跟dict相差无几 # 查看源码有更深的理解,继承dict类
其中用得比较多的是get方法和getlist方法。
<1>get方法:用来获取指定key的值,如果没有这个key,那么会返回None。
<2>getlist方法:如果浏览器上传上来的key对应的值有多个,那么就需要通过这个方法获取。
示例1:
def login(request):
print(type(request.GET)) #
print(request.GET)#
print(request.POST)#
return HttpResponse('login')
以下两句的区别
def login(request):
#username=request.GET['username'] # 若没有这个key,就抛异常
username=request.GET.get('username') # 若没有这个key,则返回None,不会抛出异常,显然这种写法更好
print(username)
return HttpResponse('login')
request.GET.get('username')这种写法,可以应用在'分页'的功能,比如:
username=request.GET.get('page',default=1) # 传入关键字参数default,若url没有page值,则url默认跳转到第1页
示例2:通过'GET'加载页面,通过'POST'获取前端传过来的数据
@require_http_methods(['GET','POST'])
def add_article(request):
if request.method=='GET': # 如果是get,加载前端页面
return render(request,'add_article.html')
else: # 若是post,则获取前端提交的数据,打印处理
title=request.POST.get('title')
content=request.POST.get('content')
# tags=request.POST.get('tags')这么写,只能获取一个
tags=request.POST.getlist('tags')
print('title','---',title)
print('content','---',content)
print('tags','---',tags)
return HttpResponse('add_article')
add_article.html
● HttpResponse对象
我们编写views,实质编写的就是'服务端'
Django服务器接收到'客户端'发送过来的请求后,会将提交上来的这些数据'封装'成一个HttpRequest对象传给'视图函数'。那么'视图函数'在处理完相关的'逻辑'后,也需要返回一个'响应'给'浏览器'。而这个响应,我们必须返回HttpResponseBase或者他的子类的对象。而HttpResponse则是HttpResponseBase用得最多的子类。那么接下来就来介绍一下HttpResponse:
属性
<1> content属性:返回内容,直接呈现在'浏览器'
示例:
from django.http import HttpResponse
def response_view(request):
response=HttpResponse() # 先实例化,生成response对象
response.content='response 测试结果' # 赋值
return response # response 测试结果
#return HttpResponse('测试结果') # 这是之前的操作方法,一样的效果
<2> status_code:返回的HTTP响应状态码
示例:
def response_view(request):
response=HttpResponse()
response.status_code=400 # "GET /response/ HTTP/1.1" 400 0
return response # 状态码为400,那么网页肯定返回错误的
<3> content_type:返回的数据的MIME类型,默认为text/html。浏览器会根据这个属性,来显示数据。如果是text/html
那么就会解析这个字符串(比如文本包含标签,那么这个标签会被渲染)
如果text/plain,那么就会显示一个纯文本。常用的Content-Type如下:
•text/html(默认的,html文件)
•text/plain(纯文本)
•text/csv(csv文件)
•text/javascript(js文件)
•multipart/form-data(文件提交)
•application/json(json传输)
•application/xml(xml文件)
示例1:
def response_view(request):
response=HttpResponse(content='
测试结果
') # 默认的content_type是'text/html'return response # 所以这里的
标签是有效的,被渲染成对应Html效果
示例2:
def response_view(request):
response=HttpResponse(content='
测试结果
', # 返回的content_type为'纯文本类型',所以标签不生效
content_type='text/plain;charset=utf-8') # 注意后面的charset(类似的参数还有'encoding')参数,这样才不会乱码
return response
<4> 设置响应头(Response Header)---response['X-Access-Token'] = 'xxxx'
示例:
def response_view(request):
response=HttpResponse(content='
测试结果
',content_type='text/plain;charset=utf-8')
response['username']='JimGreen' # 设置响应头,返回给浏览器
return response
查看'浏览器'的response header,多了 'username:JimGreen' 字段
常用方法:
<1> set_cookie:用来设置cookie信息。后面讲到授权的时候会着重讲到。 # 后面会着重讲
<2> delete_cookie:用来删除cookie信息。# 后面会讲到
<3> write:HttpResponse是一个类似于文件的对象,可以用来写入数据到数据体(content)中
实际应用中,它可以在HttpResponse的基础上,再追加文本,返回给浏览器(客户端)
示例:
def response_view(request):
response=HttpResponse(content='
测试结果
',content_type='text/plain;charset=utf-8')
response.write('这是第二个测试结果文本') #
测试结果
这是第二个测试结果文本return response
● JsonResponse类:
什么是'json',把它看成一种'数据格式'就可以了(dict的格式)
不使用JsonResponse类示例:
import json
def json_response(request):
person=dict(name='Jim Green',age=18) # dict格式
person_json=json.dumps(person) # 这里要使用json.dumps(),而不是使用json.dump()---使用这个会报错,切记
response=HttpResponse(person_json,content_type='application/json') # 返回json类型的文本
return response # {"age": 18, "name": "Jim Green"}
使用JsonResponse示例:
from django.http import JsonResponse
def json_response(request):
person=dict(name='Jim Green',age=18)
# person_json=json.dumps(person)
# response=HttpResponse(person_json,content_type='application/json')
response=JsonResponse(person) # JsonResponse()相当于以上两句的封装,一句话搞定...
return response
如果直接传入'非dict'格式,会报错:
def json_response(request):
#person=dict(name='Jim Green',age=18)
persons=[
{'name':'Jim Green','age':20}, # 使用list类型
{'name':'Kate Green','age':18}
]
response=JsonResponse(persons)
return response
'''
TypeError at /json/
In order to allow non-dict objects to be serialized set the safe parameter to False.
'''
## 处理方式:response=JsonResponse(persons,safe=False) # [{"age": 20, "name": "Jim Green"}, {"age": 18, "name": "Kate Green"}]
● 生成CSV文件---传值注意是list类型(如果传入dict类型,那么只有'key',没有'value'):
有时候我们做的网站,需要将一些数据,生成有一个CSV文件给浏览器(用户),并且是作为附件的形式下载下来。
基础示例:
def csv_response(request):
response=HttpResponse(content='test',content_type='test/csv')
return response
访问:http://127.0.0.1:8000/csv/,会自动下载附件
示例2:
def csv_response(request):
response=HttpResponse(content_type='text/csv') #不传入内容,只传入类型
# 如果不设置响应头,那么下载下来的,就是'未知文件类型'
response['Content-Disposition']='attachment;filename="test.csv"' # 单独设置响应头,指明文件类型(附件和CSV文件)
writer=csv.writer(response) # 生成writer对象
writer.writerow(['name','age']) # 使用writer对象的writerow()方法传值
writer.writerow(['age','18'])# 注意这里的类型,是list
return response
访问:http://127.0.0.1:8000/csv/,会自动下载附件,查看效果
示例3:
from django.template import loader
def csv_response(request):
response=HttpResponse(content_type='text/csv') # 声明回传浏览器的文件类型
response['Content-Disposition']="attachment;filename='abc.csv'" # 定义下载附件和文件名称
context={ # 自定义数据
'rows':[ # 这里的key,就是传给模板的var
['username','age'], # 值必须是可迭代类型
['Jim Green',18],
]
}
template=loader.get_template('abc.txt') # 加载模板,生成template对象
csv_template=template.render(context) # 调用template.render()方法,传入值
# response.write(csv_template) # 这么写可以,一个用属性传值,一个用方法传值
response.content=csv_template # 最后,赋值给response.content属性 # name;ageJim Green;20
return response
abc.txt
{% for row in rows %}
{{ row.0 }};{{ row.1 }}
{% endfor %}
生成'超大型'附件---StreamingHttpResponse('流响应')
如果想要生成大型的csv文件,使用'HttpResponse'将有可能会发生超时的情况(服务器要生成一个大型csv文件,需要的时间可能会超过浏览器默认的超时时间)。这时候我们可以借助另外一个类,叫做StreamingHttpResponse对象,这个对象是将响应的数据作为一个流返回给客户端,而不是作为一个整体返回
基础示例代码:
from django.http import HttpResponse,JsonResponse,StreamingHttpResponse
def large_csv(request):
response=StreamingHttpResponse(content_type='text/csv')
response['Content-Disposition']="attachment;filename='123.csv'"
response.streaming_content=('username,age\n','Jim Green,20\n') # 没有content属性,而是'streaming_content'属性
return response
★ 生成器'generator'小提示,以下两种写法的区别
<1> data=['data {}'.format(i) for i in range(0,100)] # 返回list类型
<2> data=('data {}'.format(i) for i in range(0,100)) # 返回generator类型
示例1:
def large_csv(request):
response=StreamingHttpResponse(content_type='text/csv')
response['Content-Disposition']="attachment;filename='123.csv'"
rows=("Row {} {}".format(row,row) for row in range(0,100000)) # 传入一个大类型数据
response.streaming_content = rows # streaming_content的值必须是可迭代对象
return response
示例2: 使用HttpResponse来处理:
def large_csv(request):
response=HttpResponse(content_type='text/csv')
response['Content-Disposition']="attachment;filename='456.csv'"
writer=csv.writer(response)
for i in range(0,10000000): # 刷新网页,可以看到,响应非常慢
writer.writerow(('i={}'.format(i)))
return response
注意:StreamingHttpResponse会启动一个进程来和客户端保持长连接,所以会很消耗资源。所以如果不是特殊要求,尽量少用这种方法,大部分的时候,HttpResponse下载可以满足需求
示例3: 课件示例,有空再看看
class Echo:
"""
定义一个可以执行写操作的类,以后调用csv.writer的时候,就会执行这个方法
"""
def write(self, value):
return value
def large_csv(request):
rows = (["Row {}".format(idx), str(idx)] for idx in range(655360))
pseudo_buffer = Echo()
writer = csv.writer(pseudo_buffer)
response = StreamingHttpResponse((writer.writerow(row) for row in rows),content_type="text/csv")
response['Content-Disposition'] = 'attachment; filename="somefilename.csv"'
return response
'''
这里我们构建了一个非常大的数据集rows,并且将其变成一个迭代器。然后因为StreamingHttpResponse的第一个参数只能是一个生成器,因此我们使用圆括号(writer.writerow(row) for row in rows),并且因为我们要写的文件是csv格式的文件,因此需要调用writer.writerow将row变成一个csv格式的字符串。而调用writer.writerow又需要一个中间的容器,因此这里我们定义了一个非常简单的类Echo,这个类只实现一个write方法,以后在执行csv.writer(pseudo_buffer)的时候,就会调用Echo.writer方法。
'''
python 装X语法:https://blog.csdn.net/xufive/article/details/102856921
● 基于'类'的视图
在写视图的时候,Django除了使用函数作为视图,也可以使用类作为视图。
使用类视图可以使用类的一些特性,比如继承等
● django.views.generic.base.View是主要的类视图,所有的类视图都是继承自他。如果我们写自己的类视图,也可以继承自他。然后再根据当前请求的method,来实现不同的方法
View支持以下方法['get','post','put','patch','delete','head','options','trace']
基础示例:
from django.views.generic import View # 自定义的'视图类'必须继承View类
class FirstView(View):
# 注意get()接收的参数
# 这里其实可以这么写 def get(self,request) # 也就是说,后面的args 和 kwargs可以忽略不写
def get(self,request,args,**kwargs): # 只允许get()请求
return HttpResponse('A ClassView Test.')
urls
......
# 调用as_view()方法---注意这里传入的是'定值',不再是函数对象---as_views
path('firstview/', views.FirstView.as_view(),name='firstview'),
由于只定义了get()方法,那么这个类就只允许get请求,使用'postman'用'post'方法访问url,
是不被允许的---405Method Not Allowed
示例2---get/post请求
views
class AddBookView(View):
def get(self,request,*args,**kwargs): # 如果是get,就加载前端模板
return render(request,'add_book.html')
def post(self,request,*args,**kwargs): # 如果是post,首先获取前端提交的数据,然后打印在'终端'
bname=request.POST.get('bname') # 服务端,既可以传数据给前端(例如context),也可以获取前端提交的数据
bauthor=request.POST.get('bauthor')
print("书名:{}---作者:{}".format(bname,bauthor))
return HttpResponse('Add Book Successful!')
urls
path('add_book_view/', views.AddBookView.as_view(),name='add_book_view') # as_view()调用的是dispatch()方法
.....
add_book.html
刷新网页,查看效果...
示例3---接收额外的参数:
views
class GetBookId(View):
def get(self,request,book_id): # 了解这种传参方式
id=request.GET.get(book_id)
return HttpResponse('The book_id is {}'.format(book_id)) # The book_id is 1
urls
......
path('book_id/', views.GetBookId.as_view(),name='get_book_id'),
示例4---对请求方法更'友好'的限制:http_method_not_allowed(self, request, *args, **kwargs)
views
class GetBookId(View):
def get(self,request,book_id):
id=request.GET.get(book_id)
return HttpResponse('The book_id is {}'.format(book_id))
# 之前采用view视图,使用的是装饰器函数对请求方法加以限制
def http_method_not_allowed(self, request, *args, **kwargs): # 如果使用get以外的方法访问,那么就返回这个response
return HttpResponse('不支持GET以外的其他请求!')
其实不管是get请求还是post请求,都会走dispatch(request,args,*kwargs)方法
所以如果实现这个方法,调用get/post方法之前,都会优先调用这个方法
示例5---调用get/post以后,做其他的事情(定制化自己的内容)---def dispatch(self, request, *args, **kwargs)
class Home(View):
# 这样这里就相当于一个装饰器的功能,可以自己定制化内容
def dispatch(self, request, args, kwargs):
# 调用父类中dispatch方法
print("before") # 使用get/post方法之前,会首先执行这个方法,所以优先打印'before'
result = super(Home, self).dispatch(request,args,kwargs) # 调用父类的dispatch()
print("after")
return result
def get(self,request):
print(request.method) # 打印get
return HttpResponse('Get_Home!')
def post(self,request):
print(request.method) # 打印post
return HttpResponse('Post_Home!')
首先使用get访问url,效果:
'''
before
GET
after
'''
使用post,效果:
'''
before
POST
after
'''
● TemplateView---返回'静态模板'的时候,很有用,方便之处在于,无需写'view',直接在'url'里写:
基础示例:这个示例并没有context传参
rom django.contrib import admin
from django.urls import path
from front import views
from django.views.generic import TemplateView # 导入TemplateView类,和View类在同一模块
urlpatterns = [
......
# 调用as_view()方法,传入'template_name'参数指定模板名称
path('about/', TemplateView.as_view(template_name='test_template_view.html'),name='about'),
]
★ 如果不需要向'模板'传递任何context,以后就建议这么写,方便快捷
<1> 传递context示例:
views
from django.views.generic import View,TemplateView
class About(TemplateView): # TemplateView其实继承了3个父类,可以看看源码的实现
template_name = 'test_template_view.html' # 指定template_name值
def get_context_data(self, **kwargs): # 重写get_context_data()方法
context={
'phone':112223334455
}
return context
'''
课件示例:
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['username'] = "黄勇"
return context
'''
urls
......
path('about/', views.About.as_view(),name='about'),
在'test_template_view.html'中挂上'phone'变量
刷新网页,查看效果
● ListView---在网站开发中,经常会出现需要列出某个表中的一些数据作为列表展示出来。
比如文章列表,图书列表等等。在Django中可以使用ListView来帮我们快速实现这种需求
首先,先往数据库写入一些'测试数据':
models
class Article(models.Model):
title=models.CharField(max_length=100)
content=models.TextField()
create_time=models.DateTimeField(auto_now_add=True)
views
写入一些数据
def add_article(request):
articles=[]
for i in range(0,101):
article=Article(title='标题:{}'.format(i),content='内容:{}'.format(i))
articles.append(article)
Article.objects.bulk_create(articles)
return HttpResponse('Add Article Successful!!!')
非常方便的快捷操作:
from django.views.generic import View,TemplateView,ListView
class ArticleListView(ListView):
template_name = 'test_list_view.html' # 传入模板名
model = Article # 指定模型
# 注意,这个值默认就是所有的记录集!!!
context_object_name = 'articles' # 指定context
paginate_by = 10 # 每一页显示几个记录
ordering = 'create_time' # 指定排序
test_list_view.html
- {{ article.title }}
{% for article in articles %}
{% endfor %}
urls
......
path('list/', views.ArticleListView.as_view()),
访问:http://127.0.0.1:8000/list
刷新网页,看看效果
'翻页'默认的url:http://127.0.0.1:8000/list/?page=1
注意,翻页的默认参数'page'是可以变了:
class ArticleListView(ListView):
template_name = 'test_list_view.html' # 传入模板名
......
page_kwarg = 'p' # 由默认的'page'变更为'p'
'''
此时访问:http://127.0.0.1:8000/list/?page=8等等类似url
会跳转到page=1的页面(因为翻页参数变更了...)
真实的url:http://127.0.0.1:8000/list/?p=1
'''
• ListView传递额外的'context'---get_context_data()
示例:
class ArticleListView(ListView):
template_name = 'test_list_view.html'
model = Article
context_object_name = 'articles'
paginate_by = 10
ordering = 'create_time'
page_kwarg = 'p'
def get_context_data(self, *, object_list=None, **kwargs):
context=super().get_context_data(object_list=None,**kwargs) # 调用父类的方法
context['username']='Jim Green' # 赋值context
'''context={
'username':'Jim Green'
} # 这里如果用这种写法,那么默认的context_object_name将
被完全覆盖'''
return context # 最后返回context
'''
这里的context实质就是dict,里面包含了很多东东
def get_context_data(self, *, object_list=None, **kwargs):
context=super().get_context_data(object_list=None,**kwargs)
context['username']='Jim Green'
print('*' * 30)
print(type(context)) #
print(context) # {'username': 'Jim Green', 'is_paginated': True, 'object_list': , , , , , , , , , ]>, 'page_obj': , 'articles': , , , , , , , , , ]>, 'view': , 'paginator': }
print('*' * 30)
return context
'''
• ListView类默认返回所有的记录集,想筛选?---get_queryset(self):
示例:
class ArticleListView(ListView):
template_name = 'test_list_view.html'
......
def get_context_data(self, *, object_list=None, **kwargs):
......
return context
def get_queryset(self):
return Article.objects.filter(id__lte=5) # 只获取id小于或等于5的数据集
刷新网页再看看效果
● 前端'分页'效果的实现
Paginator和Page类:
Paginator和Page类都是用来做分页的。他们在Django中的路径为django.core.paginator.Paginator和django.core.paginator.Page。实际的应用中,不必导入,利用context.get()来获取key就可以了
以下对这两个类的常用属性和方法做解释:
Paginator常用属性和方法---这个类代表'总的页数':
<1>count:总共有多少条数据。
<2>num_pages:总共有多少页。
<3>page_range:页面的区间。比如有三页,那么就range(1,4)。
Page常用属性和方法---这个类代表'当前页':
<1>has_next:是否还有下一页。
has_previous:是否还有上一页。
<2>next_page_number:下一页的页码。
previous_page_number:上一页的页码。
<3>number:当前页。
<4>start_index:当前这一页的第一条数据的索引值。
end_index:当前这一页的最后一条数据的索引值。
示例代码<1>:统计总共返回多少条数据
class ArticleListView(ListView):
template_name = 'test_list_view.html'
model = Article
context_object_name = 'articles'
paginate_by = 10
ordering = 'create_time'
page_kwarg = 'p'
def get_context_data(self, *, object_list=None, **kwargs):
context=super().get_context_data(object_list=None,**kwargs)
context['username']='Jim Green'
print('*' * 30)
paginator=context.get('paginator') # 首先获取paginator key
print(paginator.count) # 返回202 # range(0,101)写了两次,就是202
print('*' * 30)
return context
示例<2>:查看总的页数
def get_context_data(self, *, object_list=None, **kwargs):
....
paginator=context.get('paginator')
print(paginator.num_pages) # 21 #即总共21页
print('*' * 30)
return context
示例<3>:查看页数区间
def get_context_data(self, *, object_list=None, **kwargs):
context=super().get_context_data(object_list=None,**kwargs)
context['username']='Jim Green'
print('*' * 30)
paginator=context.get('paginator')
print(paginator.page_range) # range(1, 22) # 即0-21页的区间页数
print('*' * 30)
return context
示例<4>:判断是否有'下一页','上一页'
# url当前位于第一页
def get_context_data(self, *, object_list=None, **kwargs):
context=super().get_context_data(object_list=None,**kwargs)
context['username']='Jim Green'
print('*' * 30)
paginator=context.get('paginator')
page=context.get('page_obj') # 首先获取Page类对象
print(page.has_next()) # True # has_next()是一个方法,不是属性
print('*' * 30)
return context
## 不必纠结于是'方法'还是'属性',只要写入模板,都是'类属性'的方式处理
def get_context_data(self, *, object_list=None, **kwargs):
context=super().get_context_data(object_list=None,**kwargs)
context['username']='Jim Green'
print('*' * 30)
paginator=context.get('paginator')
page=context.get('page_obj')
print(page.has_previous()) # False # 第一页是没有'上一页'的
print('*' * 30)
return context
示例<5>:判断'上一页','下一页'的页码
def get_context_data(self, *, object_list=None, **kwargs):
context=super().get_context_data(object_list=None,**kwargs)
context['username']='Jim Green'
print('*' * 30)
paginator=context.get('paginator')
page=context.get('page_obj')
print(page.next_page_number()) # 2
print('*' * 30)
return context
def get_context_data(self, *, object_list=None, **kwargs):
context=super().get_context_data(object_list=None,**kwargs)
context['username']='Jim Green'
print('*' * 30)
paginator=context.get('paginator')
page=context.get('page_obj')
print(page.previous_page_number()) # 1 这里如果用page=1测试,就报错了,因为没有上一页页码
print('*' * 30)
return context
示例<6>:显示当前页码
def get_context_data(self, *, object_list=None, **kwargs):
context=super().get_context_data(object_list=None,**kwargs)
context['username']='Jim Green'
print('*' * 30)
paginator=context.get('paginator')
page=context.get('page_obj')
print(page.number) # 2 注意,number是属性,不是方法了
print('*' * 30)
return context
示例<7>:显示当前第一条/最后一条数据的'索引'
def get_context_data(self, *, object_list=None, **kwargs):
context=super().get_context_data(object_list=None,**kwargs)
context['username']='Jim Green'
print('*' * 30)
paginator=context.get('paginator')
page=context.get('page_obj')
print(page.start_index()) # 1
print(page.end_index()) # 2
print('*' * 30)
return context
● 使用'bootstrap'实现前端分页效果(bootstrap仅仅提供一个壳,也就是外形...)
首先,访问:https://v3.bootcss.com/getting-started/#accessibility
copy以下代码到你的'模板 test_list_view.html':
# 我们的样式,实质就是css
test_list_view.html
- {{ article.title }}
{% for article in articles %}
{% endfor %}
#
把模板打开,看看效果...
再来看看这种效果
步骤<1> 如果'当前页'有'上一页',那么页码'上一页'就正常使用;反之,就处于禁用状态;
'''刷新页面,查看效果'''
步骤<2> 添加'页码'链接
步骤<3> 删除'#'---若不删,那么url有一个'#',显然是不能接受的
{% if page_obj.has_previous %}
上一页
{% else %}
# 删除'#',添加js,表示无反应
上一页
{% endif %}
##至此 '上一页'功能 处理完毕
步骤<4> '中间页'的'初步'处理
{# 上一页 #}
......
{# 中间页码 #}
{% for page in paginator.page_range %} # 利用page_range区间,遍历获取页数number
- {{ page }}
{% endfor %}
步骤<5> '中间页'当前页面的'选中状态'
{# 中间页码 #}
{% for page in paginator.page_range %}
# 利用page_obj.number获取当前页
# 如果是当前页,就显示'选中'状态
{% if page == page_obj.number %} # 注意'='左右不能有空格
{{ page }}
{% else %}
{{ page }}
{% endif %}
{% endfor %}
同样的,删除'#'效果,添加'当前页面链接'
{# 中间页码 #}
{% for page in paginator.page_range %}
# 利用page_obj.number获取当前页
# 如果是当前页,就显示'选中'状态
{% if page == page_obj.number %} # 注意'='左右不能有空格
{{ page }}
{% else %}
# 拼接url
{{ page }}
{% endif %}
{% endfor %}
步骤<6> '下一页'的实现
{# 下一页 #}
{% if page_obj.has_next %} # 判断是否有下一页
# 如果有下一页,就返回下一页链接
下一页
{% else %}
# 如果没有'下一页',就木反应,并且标签状态为'不可用'
下一页
{% endif %}
## 至此,'分页'功能完成
完整代码如下:
- {{ article.title }}
{% for article in articles %}
{% endfor %}
{# 上一页 #}
{% if page_obj.has_previous %}
- 上一页
{% else %}
- 上一页
{% endif %}
{# 中间页码 #}
{% for page in paginator.page_range %}
{% if page == page_obj.number %}
- {{ page }}
{% else %}
- {{ page }}
{% endif %}
{% endfor %}
{# 下一页 #}
{% if page_obj.has_next %}
- 下一页
{% else %}
- 下一页
{% endif %}
基本的'分页'功能虽然完成,但是这个'功能'有一个'大缺陷',当DB的数据变得越来越庞大时,'分页'就非常多了
显然是不可接受的,现在解决这个问题.
步骤1:自定义一个函数get_pagination_data()来解决'问题'
class ArticleListView(ListView):
template_name = 'test_list_view.html'
model = Article
......
def get_context_data(self, *, object_list=None, **kwargs):
context=super().get_context_data(object_list=None,**kwargs)
context['username']='Jim Green'
print('*' * 30)
paginator=context.get('paginator')
page=context.get('page_obj')
print(paginator,page)
print('*' * 30)
return context
# 接收paginator,page_obj,并定义默认的'分页步长=2'
def get_pagination_data(self,paginator,page,around_count=2):
current_page=page.number # 定义当前页current_page
# 比如当前页是'10',遍历之后,就是(8,10),那么'10'左边的页数就是'8,9'
left_pages=range(current_page-around_count,current_page) # 定义'左边页的范围'
# 比如当前页是'10',遍历之后,就是(11,13),那么'10'左边的页数就是'11,12'
right_pages=range(current_page+1,current_page+around_count+1) # 定义'右边页的范围'
context={
'left_pages':left_pages,
'right_pages':right_pages,
'current_page':current_page,
}
return context
步骤2:在'get_context_data()'里面,调用1的方法,并且更新context(自定义的方法,是不会被调用的,所以放在会
'自动调用'的方法里面'调用'):
class ArticleListView(ListView):
template_name = 'test_list_view.html'
model = Article
......
def get_context_data(self, *, object_list=None, **kwargs):
context=super().get_context_data(object_list=None,**kwargs)
context['username']='Jim Green'
paginator=context.get('paginator')
page=context.get('page_obj')
pagination_data=self.get_pagination_data(paginator,page) # 调用自定义方法
# dict1和dict2直接的更新---python update()方法
context.update(pagination_data)# 把存储方法的var更新进context
return context
def get_pagination_data(self,paginator,page,around_count=2):
......
return context
步骤3:前端模板调用传过来的context
.html
- {{ article.title }}
{% for article in articles %}
{% endfor %}
{# 上一页 #}
......
{# 左边的页码 #}
{% for left_page in left_pages %}
- {{ left_page }}
{% endfor %}
{# 当前页码 #}
- {{ current_page }}
{# 右边页码 #}
{% for right_page in right_pages %}
- {{ right_page }}
{% endfor %}
{# 下一页 #}
......
刷新页面,可以看到效果,但是有一个'bug','当前页'为'2'的时候,'左边页'显示'0,1'这显然是不可接受的
改进示例:
class ArticleListView(ListView):
#template_name = 'test_list_view.html'
template_name = 'test.html'
model = Article
......
def get_context_data(self, *, object_list=None, **kwargs):
......
return context
def get_pagination_data(self,paginator,page,around_count=2):
current_page=page.number
num_pages=paginator.num_pages # 新定义总的页数
if current_page <= around_count+2: # 增加最左边的判断
left_pages=range(1,current_page)
else:
left_pages=range(current_page-around_count,current_page)
if current_page >= num_pages-around_count-1: # 增加最右边的判断
right_pages=range(current_page+1,num_pages+1)
else:
right_pages=range(current_page+1,current_page+around_count+1)
context={
'left_pages':left_pages,
'right_pages':right_pages,
'current_page':current_page
}
return context
刷新页面,看看效果
步骤4:增加'...'效果
示例:
class ArticleListView(ListView):
template_name = 'test.html'
model = Article
......
def get_context_data(self, *, object_list=None, **kwargs):
......
return context
def get_pagination_data(self,paginator,page,around_count=2):
current_page=page.number
num_pages=paginator.num_pages
left_has_more=False # 定义两个布尔值,默认False
right_has_more=False
if current_page <= around_count+2:
left_pages=range(1,current_page)
else:
left_has_more=True # 激活布尔值
left_pages=range(current_page-around_count,current_page)
if current_page >= num_pages-around_count-1:
right_pages=range(current_page+1,num_pages+1)
else:
right_has_more=True # 激活布尔值
right_pages=range(current_page+1,current_page+around_count+1)
context={
'left_pages':left_pages,
'right_pages':right_pages,
'current_page':current_page,
'left_has_more':left_has_more, # 传参两个参数
'right_has_more':right_has_more,
}
return context
## 分页效果"没有"完成
● 错误处理
在一些网站开发中。经常会需要捕获一些错误,然后将这些错误返回比较优美的界面,或者是将这个错误的请求做一些日志保存.
常用的错误码:
404:服务器没有指定的url。
403:没有权限访问相关的数据。
405:请求的method错误。
400:bad request,请求的参数错误。
500:服务器内部错误,一般是代码出bug了。
502:一般部署的时候见得比较多,一般是nginx启动了,然后uwsgi有问题。
在碰到比如404,500错误的时候,想要返回自己定义的模板。那么可以直接在templates文件夹下创建相应错误代码的html模板文件。那么以后在发生相应错误后,会将指定的模板返回回去
模板名称'不能随意命名',应命名为例如'404.html,403.html'
注意,在'settings'中,必须先把'debug=True'设置成'False',同时:
ALLOWED_HOSTS = ['127.0.0.1'] # 必须设置这个url,否则会报错
刷新网页,看看效果(默认的错误效果显然是不尽如人意的...),现在自定义'404'模板
重启服务(因为新增加了模板文件'404.html',若不重启,没有效果),看看效果
• 对于404和500这种自动抛出的错误。我们可以直接在templates文件夹下新建相应错误代码的模板文件。而对于其他的错误,我们可以专门定义一个app,用来处理这些错误:
errors.views
from django.shortcuts import render
def view_405(request):
# 返回模板并添加status参数,返回状态码
return render(request,'errors/405.html',status=405)
main_url
from django.contrib import admin
from django.urls import path,include
from front import views
urlpatterns = [
path('admin/', admin.site.urls),
path('', views.index),
path('errors/', include('errors.urls')), # errors app url映射
]
errors.url
from django.urls import path,include
from . import views
app_name='errors'
urlpatterns = [
# 这里注意name的值必须是str,如果这里不写双引号,就报错啦...
# 注意这里的'405.html'这种命名方式和'java,php'有点类似,是可以接受的
path('405.html', views.view_405,name='405'), # 添加模板链接
]
405模板
405请求的方法错误,请变更请求方法!
front.views
from django.shortcuts import render,reverse,redirect
from django.http import HttpResponse
def index(request):
'''
如果get到username,就返回首页;否则,就返回'自定义的错误界面'
'''
username=request.GET.get('username')
if not username:
return redirect(reverse('errors:405'))
return HttpResponse('index')
表单
单纯从前端的html来说,表单是用来提交数据给服务器的,不管后台的服务器用的是Django还是PHP语言还是其他语言。只要把input标签放在form标签中,然后再添加一个提交按钮,那么以后点击提交按钮,就可以将input标签中对应的值提交给服务器了。
Django中的表单:
Django中的表单丰富了传统的HTML语言中的表单。在Django中的表单,主要做以下两件事:
<1> 渲染表单模板。
<2> 表单验证数据是否合法
以生成'留言板'网页为示例:
步骤<1> 新建'front app',新建'forms'
forms
总览一下,操作方法与'模型'类似
from django import forms
class MessageBoardForm(forms.Form): # 继承Form类
title=forms.CharField(max_length=10,min_length=2)
content=forms.CharField(widget=forms.Textarea)# 使用widget(组件)指定'Textarea'类型
email=forms.EmailField()
reply=forms.BooleanField(required=False)# reply字段通过'required'参数取值,表示'可选','可不选'
步骤<2> '服务端'加载'MessageBoardForm'
front.views
from django.shortcuts import render
from django.views.generic import View
from .forms import MessageBoardForm
FormView用来处理'get'和'post'请求
class FormView(View):
def get(self,request):
# 生成空表单并传递context
# 这个form对象包含了不少方法和属性,可以了解
form=MessageBoardForm()
context={
'form':form
}
return render(request,'index.html',context=context)
步骤<3> 前端模板的渲染并映射url
index.html
'''
实际开发中,很少用到form.as_table,比如想插入CSS之类的样式,这里要怎么破...
'''
urls
from django.urls import path
from front import views
urlpatterns = [
path('', views.FormView.as_view()),
]
启动服务,刷新网页,看看效果(每个'字段'会'自动'添加':'符号)
可以看到,即使后端的'post'方法没有定义,但是前端依然对'字段'作了限制
比如'content'字段不能为空,'email'字段的格式'随便写'也是不能通过的
(请不要'图样图森破',以为'后端'就不用对'字段'作限制了...原因自己想...)
• 小小修改一下'forms.py',变更模板的标签(通过'label'设置中文标签)
forms
from django import forms
class MessageBoardForm(forms.Form):
# 通过label参数设置中文标签
title=forms.CharField(max_length=10,min_length=2,label='标题')
content=forms.CharField(widget=forms.Textarea,label='内容')
email=forms.EmailField(label='邮箱')
reply=forms.BooleanField(required=False,label='回复')
步骤<4> 后端'post'方法以及'异常'的编写:
views
from django.shortcuts import render
from django.views.generic import View
from .forms import MessageBoardForm
from django.http import HttpResponse
class FormView(View):
def get(self,request):
......
return render(request,'index.html',context=context)
def post(self,request):
# 这里依然涉及form对象的一些属性和方法,有空可以去了解(is_valid和cleaned_data经常搭配使用)
form=MessageBoardForm(request.POST) # 接收POST传过来的'类字典对象'
if form.is_valid(): # 验证是否可用
title=form.cleaned_data.get('title') # 利用cleaned_data属性获取对应的字段
content=form.cleaned_data.get('content')
email=form.cleaned_data.get('email')
reply=form.cleaned_data.get('reply')
# 终端获取前端传过来的数据
print('*'*40)
print(title)
print(content)
print(email)
print(reply)
print('*'*40)
return HttpResponse('留言板回复成功!!!')
else:
print(form.errors) # 如果用户发送的字段验证失败,'终端打印错误信息'
return HttpResponse('fail!')
'''
- content
- This field is required.
- title
- This field is required.
- email
- This field is required.
'''
## errors属性返回的'信息'并不友好,查看type(form.errors)
# class 'django.forms.utils.ErrorDict' ,errors实质就是'ErrorDict'的一个实例,查看源码,我们调用get_json_data()返回更加友好的错误信息
更为友好的'errors':
views
from django.shortcuts import render
from django.views.generic import View
from .forms import MessageBoardForm
from django.http import HttpResponse
class FormView(View):
......
else:
# {'content': [{'message': 'This field is required.', 'code': 'required'}], 'title': [{'message': 'This field is required.', 'code': 'required'}], 'email': [{'message': 'This field is required.', 'code': 'required'}]}
print(form.errors.get_json_data())
return HttpResponse('fail!')
'''
显然看起来更加清楚了
'''
进一步的改进:使用中文'自定义'出错内容
forms
from django import forms
class MessageBoardForm(forms.Form):
# 通过 error_messages 参数自定义'出错信息'
title=forms.CharField(max_length=10,min_length=2,label='标题',
error_messages=dict(min_length='字段不能少于2个字符'))
content=forms.CharField(widget=forms.Textarea,label='内容',
error_messages=dict(required='必须输入content字段'))
email=forms.EmailField(label='邮箱',error_messages=dict(required='必须输入email字段'))
reply=forms.BooleanField(required=False,label='回复')
'''
{'content': [{'message': '必须输入content字段', 'code': 'required'}], 'email': [{'message': '必须输入email字段', 'code': 'required'}], 'title': [{'message': 'This field is required.', 'code': 'required'}]}
'''
最终修改版:
forms
from django import forms
class MessageBoardForm(forms.Form):
这里传递的key不仅仅只有'required',比如还有'invalid'
email=forms.EmailField(label='邮箱',error_messages=dict(required='必须输入email字段'))
price=forms.FloatField(label='价格',error_messages=dict(invalid='请输入正确的浮点类型'))
views
from django.shortcuts import render
from django.views.generic import View
from .forms import MessageBoardForm
from django.http import HttpResponse
from django.forms.utils import ErrorDict
class FormView(View):
# 不再传递context=form,直接加载前端表单
def get(self,request):
return render(request,'index.html')
def post(self,request):
form=MessageBoardForm(request.POST)
if form.is_valid():# 字段的验证,在调用is_valid()的时候,就会被验证
price=form.cleaned_data.get('price')
return HttpResponse('数据提交成功!!!')
else:
print(form.errors.get_json_data()) # 最终都是调用get_json_data()方法
return HttpResponse('数据提交失败!')
html
重启服务,故意输错,看看终端的效果
'''
{'price': [{'message': '请输入正确的浮点类型', 'code': 'invalid'}], 'email': [{'message': '必须输入email字段', 'code': 'required'}]}
'''
● 另一种验证方法---验证器(validator),它的效果和'error_messages'参数类似
以下是一些常用的验证器:
<1> MaxValueValidator:验证最大值。
<2> MinValueValidator:验证最小值。
<3> MinLengthValidator:验证最小长度。
<4> MaxLengthValidator:验证最大长度。
<5> EmailValidator:验证是否是邮箱格式。
<6> URLValidator:验证是否是URL格式。
<7> RegexValidator:如果还需要更加复杂的验证,那么我们可以通过正则表达式的验证器:RegexValidator。比如现在要验证手机号码是否合格,那么我们可以通过以下代码实现:
from django import forms
from django.core import validators
class MessageBoardForm(forms.Form):
telephone=forms.CharField(validators=[validators.RegexValidator(r'1[345678]\d{9}',
message='请输入正确格式的手机号码!')])
基础示例用法:
forms
from django import forms
from django.core import validators # 导入验证器
class MessageBoardForm(forms.Form):
#email=forms.EmailField(label='邮箱',error_messages=dict(required='必须输入email字段',
#invalid='请输入正确的邮箱地址格式'))
# 通过validators参数指定验证器(list类型,传递message,自定义验证信息)
email=forms.EmailField(validators=[validators.EmailValidator(message='请输入正确的邮箱地址格式')])
views
from django.shortcuts import render
from django.views.generic import View
from .forms import MessageBoardForm
from django.http import HttpResponse
from django.forms.utils import ErrorDict
class FormView(View):
def get(self,request):
return render(request,'index.html')
def post(self,request):
form=MessageBoardForm(request.POST)
if form.is_valid():
return HttpResponse('数据提交成功!!!')
else:
print(form.errors.get_json_data()) # 依旧调用get_json_data()打印错误信息
return HttpResponse('数据提交失败!')
index.html
访问url,随便输入,终端信息:
这里多一个另外的提示,why???
{'email': [{'code': 'invalid', 'message': 'Enter a valid email address.'}, {'code': 'invalid', 'message': '请输入正确的邮箱地址格式'}]}
换成之前的 error_messages 示例,对比一下效果(基本差不多...):
from django import forms
from django.core import validators
class MessageBoardForm(forms.Form):
email=forms.EmailField(label='邮箱',error_messages=dict(required='必须输入email字段',
invalid='请输入正确的邮箱地址格式'))
# email=forms.EmailField(validators=[validators.EmailValidator(
# message='请输入正确的邮箱地址格式'
# )])
'''
{'email': [{'code': 'invalid', 'message': '请输入正确的邮箱地址格式'}]}
'''
● 自定义验证方法---实现更复杂的逻辑(单单有验证器是满足不了需求的)
实现'注册'需求,比如手机号码'telephone'字段值如果已经存在,那就提示'注册失败',终端打印'出错信息'
显然,这个需求得有数据库的支持了...
步骤1: 先定义模型(与前端提交的数据进行比对!)
front.models
from django.db import models
class User(models.Model):
# 简单定义两个字段
username=models.CharField(max_length=100)
telephone=models.CharField(max_length=11)
# 下面这句其实已经满足需求了,为了演示,就不这么写
#telephone=models.CharField(max_length=11,unique=True)
步骤2: 定义'表单'(代码与'model'类似)
front.forms
class RegisterForm(forms.Form):
username=forms.CharField(max_length=100)
telephone=forms.CharField(validators=[validators.RegexValidator(
r'1[345678]\d{9}',message='请输入正确格式的手机号码'
)])
步骤3: 后端'views',前端模板编写:
front.views
class RegisterView(View):
# get加载空表单
def get(self,request):
return render(request,'register.html')
def post(self,request):
form=RegisterForm(request.POST)
if form.is_valid():
username=form.cleaned_data.get('username')
telephone=form.cleaned_data.get('telephone')
# 把获取的字段,传给模型,插入数据库
# 这里暂未涉及对'字段'的判断
User.objects.create(username=username,telephone=telephone)
return HttpResponse('注册成功!')
else:
print(form.errors.get_json_data())
return HttpResponse('注册失败')
register.html
上述步骤完成后,刷新网页看看效果,基本的'注册'功能已经有了'雏形',下来,我们对'telephone'字段'自定义验证'
步骤4:单个字段的判断---自定义clean_field()方法,对字段进行'限制'(这个方法在form.is_valid()的时候被自动调用)
front.forms
class RegisterForm(forms.Form):
username=forms.CharField(max_length=100)
telephone=forms.CharField(validators=[validators.RegexValidator(
r'1[345678]\d{9}',message='请输入正确格式的手机号码'
)])
def clean_telephone(self): # clean_field()
# 获取telephone字段并与数据库对比
telephone=self.cleaned_data.get('telephone')
exists=User.objects.filter(telephone=telephone).exists()
if exists:
raise forms.ValidationError('{} 已经存在,请变更号码'.format(telephone))
# clean_field()方法一定要返回field...
return telephone
刷新网页,插入相同的'telephone'看看效果
步骤5:多个字段判断---重写clean()方法,对'password'字段进行确认(比如'两次密码输入不一致')
class RegisterForm(forms.Form):
username=forms.CharField(max_length=100)
telephone=forms.CharField(validators=[validators.RegexValidator(
r'1[345678]\d{9}',message='请输入正确格式的手机号码'
)])
pw1=forms.CharField(max_length=16,min_length=6) # 新增'密码'字段
pw2=forms.CharField(max_length=16,min_length=6)
def clean_telephone(self):
......
return telephone
# 如果来到了clean()方法,说明之前的每一个字段都验证成功了!
def clean(self):
# 查看源码clean()方法返回的是 self.cleaned_data
clean_data=super().clean() # 调用父类的clean()方法,返回clean_data
pw1=clean_data.get('pw1')
pw2=clean_data.get('pw2')
if pw1 != pw2:
raise forms.ValidationError(message='两次密码输入不一致!')
return clean_data # clean()返回的一定是 clean_data
前端模板修改一下:
register.html
刷新网页,看看'密码不一致'效果
需求变更为'用户名'或'手机号码'存在,就提示注册失败,并在终端输出'错误信息'
我们使用clean()方法[处理多个字段]一下子搞定,不再定义'clean_field()'对'单独字段'进行处理
from django import forms
from django.core import validators
from .models import Register
from django.db.models import Q
class RegiterForm(forms.Form):
username=forms.CharField(max_length=20)
telephone=forms.CharField(validators=[validators.RegexValidator(r'1[345678]\d{9}',
message='请输入正确格式的手机号码!')])
password1=forms.CharField(max_length=16,min_length=6)
password2=forms.CharField(max_length=16,min_length=6)
'''def clean_telephone(self):
telephone=self.cleaned_data.get('telephone')
exists=Register.objects.filter(telephone=telephone).exists()
if exists:
raise forms.ValidationError(message='手机号码已经存在,请变更手机号码!')
return telephone
def clean_username(self):
username=self.cleaned_data.get('username')
exists=Register.objects.filter(username=username).exists()
if exists:
raise forms.ValidationError(message='用户名已经存在,请变更注册名')
return username'''
def clean(self):
clean_data=super().clean()
password1=clean_data.get('password1')
password2=clean_data.get('password2')
username=clean_data.get('username')
telephone=clean_data.get('telephone')
if password1 != password2:
raise forms.ValidationError(message='密码输入不一致,请重新输入!')
exists = Register.objects.filter(Q(telephone=telephone) | Q(username=username)).exists()
if exists:
raise forms.ValidationError(message='用户名或手机号码已经存在,请变更用户名或手机号码!')
return clean_data
刷新网页,查看效果
步骤5:自定义一个更为'友好'的错误信息,之前的错误信息是这样的:
dict1={'telephone': [{'code': 'invalid', 'message': '请输入正确格式的手机号码'}], 'all': [{'code': '', 'message': '两次密码输入不一致!'}]}
我们改进一下,变成下面这样的,显然更为'友好':
{'telephone': ['请输入正确格式的手机号码'], 'all': ['两次密码输入不一致!']}
示例:
fomrs
from django import forms
from django.core import validators
from .models import User
class RegisterForm(forms.Form):
username=forms.CharField(max_length=100)
......
def clean_telephone(self):
......
return telephone
def clean(self):
......
return clean_data
def get_errors(self):# 自定义get_errors()方法,改进'错误信息'
errors=self.errors.get_json_data() # 调用 get_json_data()方法,获取'信息dict'
new_errors={} # 定义空dict,把改进后的结果,扔进来
for key,message_dicts in errors.items():
messages=[] # 收集字典的'value'值
for message_dict in message_dicts:
message=message_dict['message']
messages.append(message)
new_errors[key]=messages
return new_errors
views
class RegisterView(View):
def get(self,request):
return render(request,'register.html')
def post(self,request):
......
else:
print(form.get_errors()) # 不再是调用get_json_data()
return HttpResponse('注册失败')
'''
{'__all__': ['两次密码输入不一致!'], 'telephone': ['请输入正确格式的手机号码']}
'''
● forms.ModelForm---自定义的'表单类'继承'ModelForm',利用已有的'模型',快速生成'表单类'
示例:
front.models
from django.db import models
class Book(models.Model):
title=models.CharField(max_length=50)
page=models.IntegerField()
price=models.FloatField()
front.forms
from django import forms
from .models import Book
class AddBookForm(forms.ModelForm): # 继承ModelForm类
class Meta: # 使用Meta来定义
model=Book # 指定模型
fields='__all__' # 处理模型所有的字段
# fields=['title','page'] # 只处理'title','page'字段
# exclude=['price'] # 排除'price'字段
error_messages={ # 错误信息的设置
'page':{
'required':'请传入page参数'
},
'title':{
'max_length':'title不能超过100个字符'
}
}
front.views
后端没什么变化
这里注意没使用'get'加载前端模板,因为我们使用'postman'测试啦...
def add_book(request):
form=AddBookForm(request.POST)
if form.is_valid():
title=form.cleaned_data.get('title')
page=form.cleaned_data.get('page')
price=form.cleaned_data.get('price')
print('title-{},page-{},price{}'.format(title,page,price))
return HttpResponse('successful!')
else:
print(form.errors.get_json_data())
return HttpResponse('fail!')
'''
出错消息:
{'price': [{'code': 'required', 'message': 'This field is required.'}], 'title': [{'code': 'required', 'message': 'This field is required.'}], 'page': [{'code': 'required', 'message': '请传入page参数'}]}
'''