django[五] 视图层view
视图函数及快捷方式
视图函数,简称视图,本质上是一个简单的Python函数,它接受Web请求并且返回Web响应。
响应的内容可以是HTML网页、重定向、404错误,XML文档或图像等任何东西。但是,无论视图本身是个什么处理逻辑,最好都返回某种响应。
视图函数的代码写在哪里也无所谓,只要它在你的Python目录下面。但是通常我们约定将视图放置在项目或应用程序目录中的名为views.py的文件中。
简单的视图
from django.http import HttpResponse import datetime def current_datetime(request): now = datetime.datetime.now() html = "<html><body>It is now %s.</body></html>" % now return HttpResponse(html)
返回错误
HttpResponse的许多子类对应着除了200(代表“OK”)以外的一些常用的HTTP状态码。
为了标示一个错误,可以直接返回那些子类中的一个实例,而不是普通的HttpResponse。像下面这样:
from django.http import HttpResponse, HttpResponseNotFound def my_view(request): # ... if foo: return HttpResponseNotFound('<h1>Page not found</h1>') else: return HttpResponse('<h1>Page was found</h1>')
Django为404错误提供了一个特化的子类HttpResponseNotFound。由于一些状态码不太常用,所以不是每个状态码都有一个特化的子类。
也可以向HttpResponse的构造器传递HTTP状态码,来创建你想要的任何状态码的返回类。 像下面这样:
from django.http import HttpResponse def my_view(request): # ... # Return a "created" (201) response code. return HttpResponse(status=201)
关键是在返回中提供status=201
参数。别的什么303之类的错误都可以参照上面的例
Http404异常
class django.http.Http404
这是一个Django内置的异常类。可以在需要的地方认为弹出它,Django会捕获它,并且带上HTTP404错误码返回你当前app的标准错误页面或者自定义错误页面。像下面这样:
from django.http import Http404 from django.shortcuts import render from polls.models import Poll def detail(request, poll_id): try: p = Poll.objects.get(pk=poll_id) except Poll.DoesNotExist: raise Http404("Poll does not exist") return render(request, 'polls/detail.html', {'poll': p})
为了在Django返回404时显示自定义的HTML,可以创建一个名为404.html的HTML模板,并将其放置在模板树的顶层。 当DEBUG设置为False时,此模板将被自动使用
render()
render(request, template_name, context=None, content_type=None, status=None, using=None)[source]
结合一个给定的模板和一个给定的上下文字典,返回一个渲染后的HttpResponse对象。
必需参数:
- request:视图函数处理的当前请求,封装了请求头的所有数据,其实就是视图参数request。
- template_name:要使用的模板的完整名称或者模板名称的列表。如果是一个列表,将使用其中能够查找到的第一个模板。
可选参数:
- context:添加到模板上下文的一个数据字典。默认是一个空字典。可以将认可需要提供给模板的数据以字典的格式添加进去。这里有个小技巧,使用Python内置的locals()方法,可以方便的将函数作用于内的所有变量一次性添加。
- content_type:用于生成的文档的MIME类型。 默认为
DEFAULT_CONTENT_TYPE
设置的值。 - status:响应的状态代码。 默认为200。
- using:用于加载模板使用的模板引擎的NAME。
范例:
下面的例子将渲染模板myapp/index.html
,MIME类型为application/xhtml+xml
:
from django.shortcuts import render def my_view(request): # View code here... return render(request, 'myapp/index.html', { 'foo': 'bar', }, content_type='application/xhtml+xml')
redirect()
redirect(to, permanent=False, args, *kwargs)[source]
根据传递进来的url参数,返回HttpResponseRedirect。
参数to可以是:
- 一个模型:将调用模型的
get_absolute_url()
函数,反向解析出目的url; - 视图名称:可能带有参数:reverse()将用于反向解析url;
- 一个绝对的或相对的URL:将原封不动的作为重定向的目标位置。
默认情况下是临时重定向,如果设置permanent=True
将永久重定向。
范例:
# 调用对象的get_absolute_url()方法来重定向URL from django.shortcuts import redirect def my_view(request): ... object = MyModel.objects.get(...) return redirect(object) # 传递视图名,使用reverse()方法反向解析url def my_view(request): ... return redirect('some-view-name', foo='bar') # 重定向到硬编码的URL def my_view(request): ... return redirect('/some/url/') # 重定向到一个完整的URL def my_view(request): ... return redirect('https://example.com/') # 所有上述形式都接受permanent参数;如果设置为True,将返回永久重定向 def my_view(request): ... object = MyModel.objects.get(...) return redirect(object, permanent=True)
get_object_or_404
get_object_or_404(klass, args, *kwargs)[source]
这个方法,非常有用,请一定熟记。常用于查询某个对象,找到了则进行下一步处理,如果未找到则给用户返回404页面。
在后台,Django其实是调用了模型管理器的get()方法,只会返回一个对象。不同的是,如果get()发生异常,会引发Http404异常,从而返回404页面,而不是模型的DoesNotExist异常。
必需参数:
- klass:要获取的对象的Model类名或者Queryset等;
**kwargs
:查询的参数,格式应该可以被get()接受。
范例:
# 从MyModel中使用主键1来获取对象
from django.shortcuts import get_object_or_404 def my_view(request): my_object = get_object_or_404(MyModel, pk=1) # 这个等同于 from django.http import Http404 def my_view(request): try: my_object = MyModel.objects.get(pk=1) except MyModel.DoesNotExist: raise Http404("No MyModel matches the given query.")
# 除了传递Model名称,还可以传递一个QuerySet实例
queryset = Book.objects.filter(title__startswith='M') get_object_or_404(queryset, pk=1) # 等同于 get_object_or_404(Book, title__startswith='M', pk=1)
get_list_or_404
get_list_or_404(klass, args, *kwargs)[source]
这其实就是get_object_or_404
多值获取版本
HttpRequest对象
每当一个用户请求发送过来,Django将HTTP数据包中的相关内容,打包成为一个HttpRequest对象,并传递给每个视图函数作为第一位置参数,也就是request,供我们调用
属性
1. HttpRequest.scheme 字符串类型,表示请求的协议种类,'http'或'https'。 2. HttpRequest.body bytes类型,表示原始HTTP请求的正文。它对于处理非HTML形式的数据非常有用:二进制图像、XML等。如果要处理常规的表单数据,应该使用HttpRequest.POST。 还可以使用类似读写文件的方式从HttpRequest中读取数据,参见HttpRequest.read()。 3. HttpRequest.path 字符串类型,表示当前请求页面的完整路径,但是不包括协议名和域名。例如:"/music/bands/the_beatles/"。这个属性,常被用于我们进行某项操作时,如果不通过,返回用户先前浏览的页面。非常有用! 4. HttpRequest.path_info 在某些Web服务器配置下,主机名后的URL部分被分成脚本前缀部分和路径信息部分。path_info 属性将始终包含路径信息部分,不论使用的Web服务器是什么。使用它代替path可以让代码在测试和开发环境中更容易地切换。 例如,如果应用的WSGIScriptAlias设置为/minfo,那么HttpRequest.path等于/music/bands/the_beatles/ ,而HttpRequest.path_info为/minfo/music/bands/the_beatles/。 5. HttpRequest.method 字符串类型,表示请求使用的HTTP方法 request.method == POST 6. HttpRequest.encoding 字符串类型,表示提交的数据的编码方式(如果为None 则表示使用DEFAULT_CHARSET设置)。 这个属性是可写的,可以通过修改它来改变表单数据的编码。任何随后的属性访问(例如GET或POST)将使用新的编码方式。 7. HttpRequest.content_type Django1.10中新增。表示从CONTENT_TYPE头解析的请求的MIME类型。 8. HttpRequest.content_params Django 1.10中新增。包含在CONTENT_TYPE标题中的键/值参数字典。 9 HttpRequest.GET 一个类似于字典的对象,包含GET请求中的所有参数。 详情参考QueryDict文档。 10. HttpRequest.POST 一个包含所有POST请求的参数,以及包含表单数据的字典。 详情请参考QueryDict文档。 如果需要访问请求中的原始或非表单数据,可以使用HttpRequest.body属性。 注意:请使用if request.method == "POST"来判断一个请求是否POST类型,而不要使用if request.POST。 POST中不包含上传文件的数据。 11. HttpRequest.COOKIES 包含所有Cookie信息的字典。 键和值都为字符串。可以类似字典类型的方式,在cookie中读写数据,但是注意cookie是不安全的,因此,不要写敏感重要的信息。 12. HttpRequest.FILES 一个类似于字典的对象,包含所有上传的文件数据。 FILES中的每个键为<input type="file" name="" />中的name属性值。 FILES中的每个值是一个UploadedFile。 要在Django中实现文件上传,就要靠这个属性! 如果请求方法是POST且请求的<form>中带有enctype="multipart/form-data"属性,那么FILES将包含上传的文件的数据。 否则,FILES将为一个空的类似于字典的对象,属于被忽略、无用的情形。 13. HttpRequest.META 包含所有HTTP头部信息的字典。 可用的头部信息取决于客户端和服务器,下面是一些示例: CONTENT_LENGTH —— 请求正文的长度(以字符串计)。 CONTENT_TYPE —— 请求正文的MIME类型。 HTTP_ACCEPT —— 可接收的响应Content-Type。 HTTP_ACCEPT_ENCODING —— 可接收的响应编码类型。 HTTP_ACCEPT_LANGUAGE —— 可接收的响应语言种类。 HTTP_HOST —— 客服端发送的Host头部。 HTTP_REFERER —— Referring页面。 HTTP_USER_AGENT —— 客户端的user-agent字符串。 QUERY_STRING —— 查询字符串。 REMOTE_ADDR —— 客户端的IP地址。想要获取客户端的ip信息,就在这里! REMOTE_HOST —— 客户端的主机名。 REMOTE_USER —— 服务器认证后的用户,如果可用。 REQUEST_METHOD —— 表示请求方法的字符串,例如"GET" 或"POST"。 SERVER_NAME —— 服务器的主机名。 13. HttpRequest.resolver_match 代表一个已解析的URL的ResolverMatch实例。
由中间件设置的属性
1. HttpRequest.session SessionMiddleware中间件:一个可读写的,类似字典的对象,表示当前会话。我们要保存用户状态,回话过程等等,靠的就是这个中间件和这个属性。 2. HttpRequest.site CurrentSiteMiddleware中间件:get_current_site()方法返回的Site或RequestSite的实例,代表当前站点是哪个。 Django是支持多站点的,如果你同时上线了几个站点,就需要为每个站点设置一个站点id。 3. HttpRequest.user AuthenticationMiddleware中间件:表示当前登录的用户的AUTH_USER_MODEL的实例,这个模型是Django内置的Auth模块下的User模型。如果用户当前未登录,则user将被设置为AnonymousUser的实例。
方法
1. HttpRequest.get_host()[source] 根据HTTP_X_FORWARDED_HOST和HTTP_HOST头部信息获取请求的原始主机。 如果这两个头部没有提供相应的值,则使用SERVER_NAME和SERVER_PORT。 例如:"127.0.0.1:8000" 注:当主机位于多个代理的后面,get_host()方法将会失败。解决办法之一是使用中间件重写代理的头部 2. HttpRequest.get_port()[source] 使用META中HTTP_X_FORWARDED_PORT和SERVER_PORT的信息返回请求的始发端口。 3. HttpRequest.get_full_path()[source] 返回包含完整参数列表的path。例如:/music/bands/the_beatles/?print=true 4. HttpRequest.build_absolute_uri(location)[source] 返回location的绝对URI形式。 如果location没有提供,则使用request.get_full_path()的值。 5. HttpRequest.get_signed_cookie(key, default=RAISE_ERROR, salt='', max_age=None)[source] 从已签名的Cookie中获取值,如果签名不合法则返回django.core.signing.BadSignature。 6. HttpRequest.is_secure()[source] 如果使用的是Https,则返回True,表示连接是安全的。 7. HttpRequest.is_ajax()[source] 如果请求是通过XMLHttpRequest生成的,则返回True。 这个方法的作用就是判断,当前请求是否通过ajax机制发送过来的。 8. HttpRequest.read(size=None)[source] 9. HttpRequest.readline()[source] 10. HttpRequest.readlines()[source] 11. HttpRequest.xreadlines()[source] 12. HttpRequest.iter() 上面的几个方法都是从HttpRequest实例读取文件数据的方法。
QueryDict对象
在HttpRequest对象中,GET和POST属性都是一个django.http.QueryDict
的实例。也就是说你可以按本文下面提供的方法操作request.POST和request.GET
request.POST或request.GET的QueryDict都是不可变,只读的。如果要修改它,需要使用QueryDict.copy()方法,获取它的一个拷贝,然后在这个拷贝上进行修改操作。
from django.http import QueryDict >>> QueryDict('a=1&a=33&c=42') <QueryDict: {'a': ['1', '33'], 'c': ['42']}> # 更新 >>> QueryDict('a=1',mutable=True) <QueryDict: {'a': ['1']}> >>> q=QueryDict('a=1',mutable=True) >>> q.update({'a':33333}) >>> q <QueryDict: {'a': ['1', 33333]}> .....略
HttpResponse对象
HttpResponse类定义在django.http模块中。
HttpRequest对象由Django自动创建,而HttpResponse对象则由程序员手动创建.
我们编写的每个视图都要实例化、填充和返回一个HttpResponse对象。也就是函数的return值。
使用方法
# 传递一个字符串 >>> from django.http import HttpResponse >>> response = HttpResponse("Here's the text of the Web page.") >>> response = HttpResponse("Text only, please.", content_type="text/plain") # 设置头部字段 >>> response = HttpResponse() >>> response['Age'] = 120 >>> del response['Age'] # 告诉浏览器将响应视为文件附件 >>> response = HttpResponse(my_data, content_type='application/vnd.ms-excel') >>> response['Content-Disposition'] = 'attachment; filename="foo.xls"'
属性
HttpResponse.flush() 清空HttpResponse实例的内容。 HttpResponse.delete_cookie(key, path='/', domain=None) 删除Cookie中指定的key。 由于Cookie的工作方式,path和domain应该与set_cookie()中使用的值相同,否则Cookie不会删掉。 HttpResponse.set_cookie(key, value='', max_age=None, expires=None, path='/', domain=None, secure=None, httponly=False) 设置一个Cookie。 参数与Python标准库中的Morsel.Cookie对象相同。
HttpResponse的子类
Django包含了一系列的HttpResponse衍生类(子类),用来处理不同类型的HTTP响应。与HttpResponse相同, 这些衍生类存在于django.http之中。 class HttpResponseRedirect[source]:重定向,返回302状态码。已经被redirect()替代。 class HttpResponsePermanentRedirect[source]:永久重定向,返回301状态码。 class HttpResponseNotModified[source]:未修改页面,返回304状态码。 class HttpResponseBadRequest[source]:错误的请求,返回400状态码。 class HttpResponseNotFound[source]:页面不存在,返回404状态码。 class HttpResponseForbidden[source]:禁止访问,返回403状态码。 class HttpResponseNotAllowed[source]:禁止访问,返回405状态码。 class HttpResponseGone[source]:过期,返回405状态码。 class HttpResponseServerError[source]:服务器错误,返回500状态码。
JsonResponse类
JsonResponse是HttpResponse的一个子类,是Django提供的用于创建JSON编码类型响应的快捷类。
它从父类继承大部分行为,并具有以下不同点:
它的默认Content-Type头部设置为application/json。
它的第一个参数data,通常应该为一个字典数据类型。 如果safe参数设置为False,则可以是任何可JSON 序列化的对象。
encoder默认为django.core.serializers.json.DjangoJSONEncoder
,用于序列化数据。
>>> from django.http import JsonResponse >>> response = JsonResponse({'foo': 'bar'}) >>> response.content b'{"foo": "bar"}'
若要序列化非dict对象,必须设置safe参数为False:
>>> response = JsonResponse([1, 2, 3], safe=False)
如果不传递safe=False,将抛出一个TypeError。
如果你需要使用不同的JSON 编码器类,可以传递encoder参数给构造函数:
>>> response = JsonResponse(data, encoder=MyJSONEncoder)
StreamingHttpResponse类
StreamingHttpResponse类被用来从Django响应一个流式对象到浏览器。如果生成的响应太长或者是占用的内存较大,这么做可能更有效率。 例如,它对于生成大型的CSV文件非常有用。
StreamingHttpResponse不是HttpResponse的衍生类(子类),因为它实现了完全不同的应用程序接口。但是,除了几个明显不同的地方,两者几乎完全相同。
FileResponse
文件类型响应。通常用于给浏览器返回一个文件附件。
FileResponse是StreamingHttpResponse的衍生类,为二进制文件专门做了优化。
FileResponse需要通过二进制模式打开文件,如下:
>>> from django.http import FileResponse >>> response = FileResponse(open('myfile.png', 'rb'))
文件上传
html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>upload file</title> </head> <body> <form action="/upload_file/" method="POST" enctype="multipart/form-data" title="fansu"> {% csrf_token %} <input type="text" name="title" size="30"> <input type="file" name="file" title="11111"> <input type="submit" value="提交"> </form> </body> </html>
UploadedFile UploadedFile是类文件对象,具有以下方法和属性: UploadedFile.read() 读取整个上传文件的数据,文件较大时慎用。 UploadedFile.multiple_chunks(chunk_size=None) 判断文件是否足够大,一般为2.5M UploadedFile.chunks(chunk_size=None) 返回一个生成器对象,当multiple_chunks()为True时应该使用这个方法来代替read(). UploadedFile.name 上传文件的name。 UploadedFile.size 上传文件的大小。 UploadedFile.content_type 上传文件时的content_type报头,例如(e.g. text/plain or application/pdf). UpladedFile.charset 编码
利用form处理上传文件
首先,写一个form模型,它必须包含一个FileField:
# forms.py from django import forms class UploadFileForm(forms.Form): title = forms.CharField(max_length=50) file = forms.FileField()
处理这个表单的视图将在request.FILES
中收到文件数据,可以用request.FILES['file']
来获取上传文件的具体数据,其中的键值‘file’是根据file = forms.FileField()
的变量名来的。
注意:request.FILES
只有在请求方法为POST,并且提交请求的<form>
具有enctype="multipart/form-data"
属性时才有效。 否则,request.FILES将为空。
下面是一个接收上传文件的视图范例:
from .forms import UploadFileForm from django.http import HttpResponseRedirect, JsonResponse from django.shortcuts import render from django.urls import reverse def upload_file(request): if request.method == 'POST': form = UploadFileForm(request.POST, request.FILES) # 注意获取数据的方式 if form.is_valid(): handle_uploaded_file(request.FILES['file']) return HttpResponseRedirect(reverse('/upload_file/')) else: return JsonResponse(form.errors, safe=False) else: form = UploadFileForm() return render(request, 'upload.html', {'form': form}) def handle_uploaded_file(f): with open(f.name, 'wb+') as destination: for chunk in f.chunks(): destination.write(chunk)
遍历UploadedFile.chunks()
,而不是直接使用read()
方法,能确保大文件不会占用系统过多的内存。
利用modelForm
models.py
from django.db import models # Create your models here. class ImageFile(models.Model): image = models.ImageField(upload_to='static/images/%Y/%m/%d')
forms.py
from .models import ImageFile class ImageFileForm(forms.ModelForm): class Meta: model = ImageFile fields = '__all__'
views.py
from .forms import ImageFileForm def upload_img(request): if request.method == 'POST': form = ImageFileForm(request.POST or None, request.FILES or None) if form.is_valid(): image = form.save() return JsonResponse(image.image.url, safe=False) else: return JsonResponse(form.errors) else: form = ImageFileForm() return render(request, 'upload_img.html', {'form': form})
区别就是使用了modelform后就不用写文件保存的部分了
html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>upload file</title> </head> <body> <form action="/upload_img/" method="POST" enctype="multipart/form-data" title="fansu"> {% csrf_token %} <input type="text" name="title" size="30"> <input type="file" name="image" title="11111"> <input type="submit" value="提交"> </form> </body> </html>
生成CSV及PDF文件
CSV
Python自带处理CSV文件的标准库csv。csv模块的CSV文件创建功能作用于类似于文件对象创建,并且Django的HttpResponse对象也是类似于文件的对象
def some_streaming_csv_view(request): # Create the HttpResponse object with the appropriate CSV header. response = HttpResponse(content_type='text/csv') response['Content-Disposition'] = 'attachment; filename="somefilename.csv"' writer = csv.writer(response) writer.writerow(['First row', 'Foo', 'Bar', 'Baz']) writer.writerow(['Second row', 'A', 'B', 'C', '"Testing"', "Here's a quote"]) return response
响应对象的MIME类型设置为text/csv,告诉浏览器,返回的是一个CSV文件而不是HTML文件。
响应对象设置了附加的Content-Disposition协议头,含有CSV文件的名称。文件名随便取,浏览器会在“另存为...”对话框等环境中使用它。
要在生成CSV的API中使用钩子非常简单:只需要把response作为第一个参数传递给csv.writer。csv.writer方法接受一个类似于文件的对象,而HttpResponse对象正好就是这么个东西。
对于CSV文件的每一行,调用writer.writerow,向它传递一个可迭代的对象比如列表或者元组。
CSV模板会为你处理各种引用,不用担心没有转义字符串中的引号或者逗号。只需要向writerow()传递你的原始字符串,它就会执行正确的操作。
当处理大尺寸文件时,可以使用Django的StreamingHttpResponse类,通过流式传输,避免负载均衡器在服务器生成响应的时候断掉连接,提高传输可靠性。
在下面的例子中,利用Python的生成器来有效处理大尺
def some_streaming_csv_view1(request): """A view that streams a large CSV file.""" # Generate a sequence of rows. The range is based on the maximum number of # rows that can be handled by a single sheet in most spreadsheet # applications. rows = (["Row {}".format(idx), str(idx)] for idx in range(65536)) 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
可以通过开源的Python PDF库ReportLab
来实现PDF文件的动态生成
pip install reportlab
ReportLab的API可以处理于类似于文件(file-like)的对象。下面是一个 “Hello World”的例子
from reportlab.pdfgen import canvas from django.http import HttpResponse def some_view(request): # 创建带有PDF头部定义的HttpResponse对象 response = HttpResponse(content_type='application/pdf') response['Content-Disposition'] = 'attachment; filename="somefilename.pdf"' # 创建一个PDF对象,并使用响应对象作为它要处理的‘文件’ p = canvas.Canvas(response) # 通过PDF对象的drawString方法,写入一条信息。具体参考模块的官方文档说明。 p.drawString(100, 100, "Hello world.") # 关闭PDF对象 p.showPage() p.save() return response
相关说明:
- 响应对象的MIME类型为
application/pdf
。 这会告诉浏览器,文档是个PDF文件而不是HTML文件。 - 响应对象设置了附加的
Content-Disposition
协议头,含有PDF文件的名称。 - 文件名可以是任意的,浏览器会在“另存为...”对话框等中使用。
Content-Disposition
以'attachment'开头,强制让浏览器弹出对话框来提示或者确认。- Canvas函数接受一个类似于文件的对象,而HttpResponse对象正好合适。
- 最后,在PDF文件上调用showPage()和save()方法非常重要。
- 注意:ReportLab并不是线程安全的。
遇到复杂pdf,可以考虑使用io库作为PDF文件的临时保存地点。这个库提供了一个类似于文件的对象接口,非常实用。
from io import BytesIO from reportlab.pdfgen import canvas from django.http import HttpResponse def some_view(request): # Create the HttpResponse object with the appropriate PDF headers. response = HttpResponse(content_type='application/pdf') response['Content-Disposition'] = 'attachment; filename="somefilename.pdf"' buffer = BytesIO() # Create the PDF object, using the BytesIO object as its "file." p = canvas.Canvas(buffer) # Draw things on the PDF. Here's where the PDF generation happens. # See the ReportLab documentation for the full list of functionality. p.drawString(100, 100, "Hello world.") # Close the PDF object cleanly. p.showPage() p.save() # Get the value of the BytesIO buffer and write it to the response. pdf = buffer.getvalue() buffer.close() response.write(pdf) return response