Django视图将URL转换为PDF使用PyQt

1 投票
2 回答
607 浏览
提问于 2025-04-17 21:26

我正在尝试写一个Django的视图,用来返回一个网址的PDF文件。

我使用的是PyQt的webview.print来生成PDF,但我不太确定怎么把这个PDF传递给Django的响应。我试过用QBuffer,但总是搞不定。

这是我目前写的视图:

def pdf(request):
    app = QApplication(sys.argv)

    bufferPdf = QBuffer()
    bufferPdf.open(QBuffer.ReadWrite)

    web = QWebView()
    web.load(QUrl("http://www.google.com")) #the desired url. 

    printer = QPrinter()
    printer.setPageSize(QPrinter.Letter)
    printer.setOrientation(QPrinter.Landscape);
    printer.setOutputFormat(QPrinter.PdfFormat)
    printer.setOutputFileName("file.pdf")

    def convertIt():
        web.print_(printer)
        print "Pdf generated"
        QApplication.exit()

    QObject.connect(web, SIGNAL("loadFinished(bool)"), convertIt)

    bufferPdf.seek(0)
    result = bufferPdf.readData(0)
    bufferPdf.close()

    sys.exit(app.exec_())

    response =  HttpResponse(result, mimetype='application/pdf')
    response['Content-Disposition'] = 'attachment; filename=coupon.pdf'
    return response

提前谢谢你们。

2 个回答

0

这是你例子的一个改写,应该能满足你的需求:

import sys
from PyQt4 import QtCore, QtGui, QtWebKit

class WebPage(QtWebKit.QWebPage):
    def __init__(self):
        QtWebKit.QWebPage.__init__(self)
        self.printer = QtGui.QPrinter()
        self.printer.setPageSize(QtGui.QPrinter.Letter)
        self.printer.setOrientation(QtGui.QPrinter.Landscape);
        self.printer.setOutputFormat(QtGui.QPrinter.PdfFormat)
        self.mainFrame().loadFinished.connect(self.handleLoadFinished)

    def start(self, url):
        self.mainFrame().load(QtCore.QUrl(url))
        QtGui.qApp.exec_()

    def handleLoadFinished(self):
        temp = QtCore.QTemporaryFile(
            QtCore.QDir.temp().filePath('webpage.XXXXXX.pdf'))
        # must open the file to get the filename.
        # file will be automatically deleted later
        temp.open()
        self.printer.setOutputFileName(temp.fileName())
        # ensure that the file can be written to
        temp.close()
        self.mainFrame().print_(self.printer)
        temp.open()
        self.pdf = temp.readAll().data()
        QtGui.qApp.quit()

def webpage2pdf(url):
    if not hasattr(WebPage, 'app'):
        # can only have one QApplication, and it must be created first
        WebPage.app = QtGui.QApplication(sys.argv)
    webpage = WebPage()
    webpage.start(url)
    return webpage.pdf

if __name__ == '__main__':

    if len(sys.argv) > 1:
        url = sys.argv[1]
    else:
        url = 'http://www.google.com'

    result = webpage2pdf(url)

    response =  HttpResponse(result, mimetype='application/pdf')
    response['Content-Disposition'] = 'attachment; filename=coupon.pdf'
    # do stuff with response...
3

ekhumoro 提供的解决方案是错误的。他给出的代码可以在命令行中运行,但在 Django 的视图中根本无法工作。

很多人都注意到,把 Django 和 QT 线程应用结合起来并不容易,甚至可能根本不可能。你看到的错误就是尝试这样做时常见的一个例子。

在我自己的项目中,我尝试了很多不同的代码组织和分组方式,但始终没有找到解决办法。问题似乎在于(我不是 QT 专家,如果有人有更多信息请纠正我)事件驱动的 QT 应用(任何使用 WebKit 的东西都使用 QT 的事件模型)是围绕一个有效的单例 "QApplication" 构建的。你无法控制这个子应用什么时候退出,以及它的各种资源什么时候被释放。因此,任何使用这个库的多线程应用都需要非常小心地管理它的资源——而在处理各种网络应用的过程中,你对这些资源的控制几乎为零。

一个可能的(虽然有点混乱和不专业)解决方案是创建一个接受命令行参数的脚本,然后在 Django 中作为一个官方的子进程调用这个脚本。你可以使用临时文件来输出结果,然后再把这些结果加载到你的应用中。在读取事件后,你可以直接删除磁盘上的文件。虽然有点麻烦,但有效。

我个人很想听听任何确切知道为什么这这么难,或者有合适解决方案的人——在 Stackoverflow 上有很多关于这个问题的讨论,但大多数都是错误或不完整的解释……

撰写回答