Преобразование HTML в PDF на лету с использованием Python и HTMLDOC - PullRequest
0 голосов
/ 17 января 2012

Я создал приложение Django для клиента около года назад. Теперь он перепродал заявление в какое-то сверхсекретное правительственное агентство, имя которого они даже не назвали.

Часть приложения динамически генерирует PDF-файлы, используя библиотеку python xhtml2pdf (pisa). Правительству не нравится эта библиотека, они не скажут мне почему, они сказали, что я должен использовать HTMLDOC для создания PDF.

В этой библиотеке не так много документации, но, прочитав пример PHP, похоже, что вы можете просто общаться с ней через оболочку, поэтому она должна работать с Python. Тем не менее, я с трудом передаю HTML в HTMLDOC. Похоже, HTMLDOC будет принимать только файл, но мне действительно нужно передать html в виде строки, поскольку он генерируется динамически. (Или запишите html-строку во временный файл, а затем передайте этот временный файл в HTMLDOC).

Я думал, что StringIO будет работать для этого, но я получаю ошибку. Вот что у меня есть:

def render_to_pdf(template_src, context_dict):
    template = get_template(template_src)
    context = Context(context_dict)
    html  = template.render(context)
    result = StringIO.StringIO(html.encode("utf-8"))
    os.putenv("HTMLDOC_NOCGI", "1")

    #this line throws "[Errno 2] No such file or directory"
    htmldoc = subprocess.Popen("htmldoc -t pdf --quiet '%s'" % result, stdout=subprocess.PIPE).communicate()

    pdf = htmldoc[0]
    result.close()
    return HttpResponse(pdf, mimetype='application/pdf')

Буду очень признателен за любые идеи, советы или помощь.

Спасибо.

UPDATE

Трассировка стека:

Environment:


Request Method: GET
Request URL: (redacted)

Django Version: 1.3 alpha 1 SVN-14921
Python Version: 2.6.5
Installed Applications:
['django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.sites',
 'django.contrib.messages',
 'django.contrib.admin',
 'application']
Installed Middleware:
('django.middleware.common.CommonMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware')


Traceback:
File "/usr/local/lib/python2.6/dist-packages/django/core/handlers/base.py" in get_response

  111. response = callback(request, *callback_args, **callback_kwargs)

File "/usr/local/lib/python2.6/dist-packages/django/contrib/auth/decorators.py" in _wrapped_view

  23. return view_func(request, *args, **kwargs)

File "/home/ascgov/application/views/pdf.py" in application_pdf

  90. 'user':owner})

File "/home/ascgov/application/views/pdf.py" in render_to_pdf

  53. htmldoc = subprocess.Popen("/usr/bin/htmldoc -t pdf --quiet '%s'" % result, stdout=subprocess.PIPE).communicate()

File "/usr/lib/python2.6/subprocess.py" in __init__

  633. errread, errwrite)

File "/usr/lib/python2.6/subprocess.py" in _execute_child

  1139. raise child_exception

Exception Type: OSError at /pdf/application/feed-filtr/
Exception Value: [Errno 2] No such file or directory

Ответы [ 2 ]

3 голосов
/ 17 января 2012

Во-первых, первый аргумент subprocess.Popen обычно должен быть списком (если только вы не передаете shell=True).No such file or directory почти наверняка вызвано отсутствием в системе файла с именем "htmldoc -t pdf --quiet '... (он пытается найти и запустить программу с именем для всего строкового значения).

Во-вторых, если вы дадите htmldoc html на свой стандартный ввод, он выплеснет PDF на свой стандартный вывод, что позволит избежать необходимости во временном файле.

Попробуйте (не проверено):

htmldoc = subprocess.Popen(
  ['/usr/bin/htmldoc', '-t', 'pdf', '--webpage', '-'], 
  stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
stdout, stderr = htmldoc.communicate(html)

Примечание: замените /usr/bin/htmldoc реальным путем к htmldoc в вашей системе.

Аргумент - для программы htmldoc указывает ей читать из stdin.Вы передадите строковое значение html (html) в stdin htmldoc в качестве аргумента для вызова htmldoc.communicate.Полученный вывод PDF должен быть доступен в stdout, а любые другие сообщения или статистика в stderr.

Редактировать : документация кажется немного странной, но ее довольно много.Возможно, вам повезет больше с html на одной странице или pdf версиями или справочной страницей .

Также обязательно передайте строку или что-то подобное в стандартный поток процесса htmldoc.Передача объекта StringIO напрямую, как было показано в моем предыдущем фрагменте кода, не будет работать.

0 голосов
/ 17 января 2012

Blergh.Какое ужасное требование и ужасная программа.

Кажется, нет никакого способа принять содержимое для преобразования в качестве параметра командной строки.Кажется, однако, что он принимает URL.Таким образом, возможно, вы могли бы передать созданный URL-адрес, который ссылается на представление на том же сервере, и в этом втором представлении вывести ваш визуализированный шаблон, который затем выбирается HTMLDOC, запущенным из первого представления.Просто знайте, что это не будет работать с сервером разработки, так как он однопоточный, поэтому представления будут всегда ждать друг друга.

...