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

  

 PDF

可以通过开源的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

  

 

 

 

 

posted @ 2019-06-11 11:11  richardzgt  阅读(308)  评论(0编辑  收藏  举报