Generate PDFs with Python using Anvil
Tags: python, pdf, webdev
Published: 2020-11-13 10:56:32

---
title: Generate PDFs with Python using Anvil
summary: |
    Build a web app that generates a PDF, downloads it, and emails it as an attachment - all in Python! No JS required.
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/sqwkabq41iddsh1kgpbp.png
canonical_url: https://anvil.works/learn/tutorials/pdfs
published: true
---

# PDFs Made Easy

Generating PDF documents in Python can be a pain, with lots of janky dependencies and HTML generation. I'm going to show you a much easier way with nothing but Python!

Using [Anvil](https://anvil.works), the pure-Python full-stack web app tool, [generating a PDF document](https://anvil.works/docs/media/creating_pdfs) is easy:

 1. Design a page in Anvil's **drag-and-drop designer**
 2. Render it to PDF with one function call
 3. Download it in the browser, or send it by email, or store it in a database, or...

In this tutorial, we'll build a simple web app where users can register for an event and download their ticket as a PDF. Follow along with the tutorial, or clone and try out the finished app:

### [Open the App in Anvil >>](https://anvil.works/build#clone:3GTU3RKLSYDYSHUV=FSKRYZVOYLAGKZGXGBFBM2HQ)

![](https://anvil.works/learn/tutorials/img/pdfs/pdfs-0-new.gif)


> Completely new to Anvil? You might want to try one of our ['Start Here' tutorials](https://anvil.works/learn/tutorials#starthere)


---

## Step 1 - Design your PDF

First, let's design a Form that we want to convert to a PDF ticket. Add a new Blank Panel Form to the app and rename it 'Ticket'. 

We can design our Form however we like. To make the Form look like a ticket, I've added a Card with a dotted border and a Label that says 'ADMIT ONE'. We should also add Labels to display the user's name (`name_label`) and the date of the event (`date_label`):

![](https://anvil.works/learn/tutorials/img/pdfs/pdfs-1-new.gif)

We should change the `__init__` of the Ticket Form so that it takes strings called `name` and `date` as arguments and displays them on the labels we just created. Switch to Code View in the Form Editor and change the `__init__` function of our Ticket class so it looks something like this:

```python
class Ticket(TicketTemplate):
  def __init__(self, name, date, **properties):
    # Set Form properties and Data Bindings.
    self.init_components(**properties)
    self.name_label.text = name
    self.date_label.text = date
```


---

## Step 2 - Create the user interface

Let's now build a UI so that users can generate and download the ticket as a PDF. We'll design a simple registration page for users to put in their name and email address and choose the date of the event they will attend. 

Switch to Form1 in the App Browser. We need two TextBoxes so users can fill in their name and email address and two Labels for these. We'll name the TextBoxes `name_box` and `email_box`. We'll also need a DropDown named `date_dropdown` so that users can choose the date of the event.

![](https://anvil.works/learn/tutorials/img/pdfs/pdfs-2-1-new.gif)

We should populate the __items__ property of our DropDown with a few dates. Each item in the DropDown should be on a separate line. We can also include a placeholder that says 'choose date'.

![](https://anvil.works/learn/tutorials/img/pdfs/pdfs-2-2.png)

Finally, let's add a Button called `register_button`, so users can register and download their PDF ticket. Let's add a `click` event for this Button that just displays our PDF form, so we can see what it will look like when it's downloaded. 

```python
  def register_button_click(self, **event_args):
    """This method is called when the button is clicked"""
    name = self.name_box.text
    date = self.date_dropdown.selected_value
    open_form('Ticket', name, date)
```

Now, run the app! Fill in a name, select a date and click REGISTER to see with the ticket looks like.

![](https://anvil.works/learn/tutorials/img/pdfs/pdfs-2-3.gif)

---

## Step 3 - Render and download the PDF 

Our ticket is looking good! Now let's turn it into a PDF. Rather than displaying the Ticket form in the browser, we'll define a server function to render it as a PDF document.

We can only generate a PDF from [Server code](https://anvil.works/docs/server), so we'll first need to add a Server Module to our app. We define a function which calls `anvil.pdf.render_form()`. This method returns a [Media object](https://anvil.works/docs/media), which means we can pass it back to the browser, download it or send it as an email attachment. We also need to decorate our fuction with `@anvil.server.callable` so we can call the function from client code.

Here's what our server code looks like:

```python
import anvil.pdf

@anvil.server.callable
def create_pdf(name, date):
    pdf = anvil.pdf.render_form('Ticket', name, date)
    return pdf
```
 
Let's call this function from the registration form then download the generated PDF. We use `anvil.server.call()` to call our server function and generate the PDF, and `anvil.media.download()` to save it. The `click` event should now look like this:

```python
  def register_button_click(self, **event_args):
    """This method is called when the button is clicked"""
    name = self.name_box.text
    date = self.date_dropdown.selected_value

    pdf = anvil.server.call('create_pdf', name, date)
    anvil.media.download(pdf)
```

That's all we need to do to render a Form as a PDF. And we can download it with just one line of code!

Let's test it out. Run the app again, fill in the form and click REGISTER. This time, the ticket will download.

![](https://anvil.works/learn/tutorials/img/pdfs/pdfs-3-1.gif)


## Step 4 - Send your PDF as an email attachment

Once we've rendered our Form as a PDF, it's easy to send an email with the document attached.

We first need to add the [Email Service](https://anvil.works/docs/email) to our app. In the App Browser, click the + next to SERVICES and click on Email. To test out the app, we can check Test Mode so that all emails are redirected to us.

![](https://anvil.works/learn/tutorials/img/pdfs/pdfs-4-1.gif)

We can then write a function in the Server Module which calls `create_pdf` and then sends the rendered PDF as an email attachment:

```python
@anvil.server.callable
def send_pdf_email(email, name, date):
  pdf = create_pdf(name, date)
  anvil.email.send(
    from_address='no-reply',
    from_name='Events', 
    to=email, 
    subject='Your Ticket',
    text='Thanks for registering!. Your ticket is attached to this email.',
    attachments=pdf
  )
  return pdf
```


> **Note:** To avoid being used for Spam, by default `anvil.email.send()` sends all email to the app owner (ie you) if you're using the Free plan. You can deliver email to other people by [configuring an SMTP server in the Email service](https://anvil.works/docs/email/sending_and_receiving#custom-mail-server) (SendGrid's Free plan works great for this), or by upgrading to a paid Anvil plan.

Now let's modify the `register_button_click` method in Form1 so that it calls the `send_pdf_email` function we just wrote. Since `send_pdf_email` returns the rendered PDF, we just need to swap it with `create_pdf` and pass in `email`:

```python
  def register_button_click(self, **event_args):
    """This method is called when the button is clicked"""
    name = self.name_box.text
    date = self.date_dropdown.selected_value
    email = self.email_box.text

    pdf = anvil.server.call('send_pdf_email', email, name, date)
    anvil.media.download(pdf)
```

We can now run the app again and our PDF ticket will be emailed to us. As long as we are in Test Mode, the ticket will always be emailed us no matter what email address we type in.

---

## Step 5 - Check the input

We want to make sure that the user has actually filled in the registration form, so let's add in a check before we generate the PDF ticket. We'll alert the user if they haven't filled the form, but if they have, we'll tell them that their ticket will be downloaded and emailed to them:

```python
 def register_button_click(self, **event_args):
    """This method is called when the button is clicked"""
    name = self.name_box.text
    email = self.email_box.text
    date = self.date_dropdown.selected_value
    
    if name and email and date:
      alert(f'Thanks for registering! Your ticket is downloading and will be sent to {email}.')

      pdf = anvil.server.call('send_pdf_email', email, name, date)
      anvil.media.download(pdf)

    else:
        alert('You have not completed all required fields')
```

Try running the app again, but this time, leave at least one of the TextBoxes blank.

![](https://anvil.works/learn/tutorials/img/pdfs/pdfs-5.gif)

---

## Step 6 - Optional: Set the PDF filename 

Our app now generates, downloads and emails a PDF attachment when a user registers for our event. However, the PDF is called 'print.pdf' when we download it, which isn't very descriptive. If we want to specify settings for our PDF document, we can use [`PDFRender()`](https://anvil.works/docs/media/creating_pdfs#customising-settings). Let's modify our `create_pdf` function and specify a filename using `PDFRenderer()`.

First add `from anvil.pdf import PDFRenderer` to the top of the Server code then modify the `create_pdf` function:

```python
import anvil.pdf
from anvil.pdf import PDFRenderer

@anvil.server.callable
def create_pdf(name, date):
    pdf = PDFRenderer(filename=f'{name} Ticket.pdf').render_form('Ticket', name, date)
    return pdf
```

Now when our ticket is downloaded, it will have a more useful name.


---

## Step 7 - Run the finished app

We've now created a simple app which converts an Anvil form to a downloadable PDF and sends it as an email attachment. Let's test out the finished app.

![](https://anvil.works/learn/tutorials/img/pdfs/pdfs-0-new.gif)


---

## That's it! 

Your app is already live on the internet. Go to [Publish App](https://anvil.works/docs/deployment) in the Gear Menu for details.

You can check out the finished app here:

### [Open the Finished App in Anvil >>](https://anvil.works/build#clone:3GTU3RKLSYDYSHUV=FSKRYZVOYLAGKZGXGBFBM2HQ)


---

### What's Next?

If you thought this was cool, check out the rest of Anvil! 

Head to the [Anvil Learning Centre](https://anvil.works/learn/tutorials) for more tutorials, or head to our [examples](https://anvil.works/learn/examples) page to see how to build some complex apps in Anvil.

### [More Anvil Tutorials >>](https://anvil.works/learn)