Using Python to Generate Google Docs for Real Estate Documents
A detailed tutorial on using Python to build a real estate document generator. Learn how to automate document creation for your real estate transactions with this flexible script.
Last updated: July 31, 2024
In the fast-paced world of real estate, efficiency is key. I remember a time when I was swamped with deals and spent hours manually creating documents. Automating document generation not only saved me significant time but also reduced errors that often crept in due to repetitive manual tasks. In this tutorial, I'll walk you through how I used Python to create Google Docs for my real estate business. You'll be able to pass a Google Doc template and any data you want to it. This guide assumes you have a basic understanding of Python and Google APIs.
What You'll Need
Before we dive in, make sure you have the following:
- A Google account
- Google Cloud project with Google Docs API enabled
- Python installed on your computer
google-auth
,google-auth-oauthlib
,google-auth-httplib2
, andgoogle-api-python-client
libraries installed
Step 1: Setting Up Your Google Cloud Project
- Create a New Project: Go to the Google Cloud Console and create a new project.
- Enable the Google Docs/Drive APIs: Navigate to the API library and enable the Google Docs and Drive APIs for your project. Make sure it says API enabled like this:
- Create a Service Account:
- Navigate to "IAM & Admin" and click "Service Accounts". At the top of the screen, you will see "Create Service Account". It should look something like this:
- Fill in the account details, give it whatever name you'd like
- Grant the service account the appropriate roles. You will need to give it 'Editor' and 'Viewer' access at the minimum.
- Give yourself access to the service account. I put my personal email in both of these roles, you can add others who may be working on the project here as well.
- Navigate to "IAM & Admin" and click "Service Accounts". At the top of the screen, you will see "Create Service Account". It should look something like this:
- Create Credentials for the Service Account:
- Click on the service account you just created. Click the top where it says "Keys". There is a button that says "Add Key":
- There is an option for "JSON". It will download a file, save it securely.
- Click on the service account you just created. Click the top where it says "Keys". There is a button that says "Add Key":
I still remember the first time I navigated the Google Cloud Console, it felt like deciphering a treasure map. Trust me, it gets easier with practice.
Step 2: Installing the Necessary Python Libraries
Open your terminal inside you project and install the required libraries. I do this inside a virtual environment, which you can read more about here:
pip install google-auth google-auth-oauthlib google-auth-httplib2 google-api-python-client
Step 3: Authenticating and Authorizing Your Script
Create a Python script and add the following code to authenticate and authorize your script:
import json
import logging
from googleapiclient.discovery import build
from google.oauth2.service_account import Credentials
# Set up logging
logging.basicConfig(level=logging.INFO)
# Set up Google Docs and Drive API
SCOPES = [
"https://www.googleapis.com/auth/documents",
"https://www.googleapis.com/auth/drive",
]
SERVICE_ACCOUNT_FILE = "service-account.json"
TEMPLATE_DOCUMENT_ID = "1M251-WfuC5yDubqfUiK95A-R3y0yGqlf1X-QXvKfe2I"
EMAIL = "your-email@example.com"
credentials = Credentials.from_service_account_file(SERVICE_ACCOUNT_FILE, scopes=SCOPES)
delegated_credentials = credentials.with_subject(EMAIL)
# Build the service clients with the delegated credentials
docs_service = build("docs", "v1", credentials=delegated_credentials)
drive_service = build("drive", "v3", credentials=delegated_credentials)
Delegating your credentials allows the service user to add the created documents to your google account. You can also share the document from the service account but you may run into sharing limits on the Google Docs API.
Step 4: Creating and Copying a Google Doc Template
Create functions to copy a document, and share it with a user if your use case needs to:
#This allows us to keep the template intact and work off a copy
def create_document_copy(template_id, new_title):
body = {"name": new_title}
copied_file = drive_service.files().copy(fileId=template_id, body=body).execute()
return copied_file["id"]
#In some cases you may want to share the document with other people. Be aware in testing you may run into limits set by Google Docs API sharing quot
def share_document_with_user(file_id, user_email):
permissions = {"type": "user", "role": "writer", "emailAddress": user_email}
drive_service.permissions().create(
fileId=file_id, body=permissions, fields="id"
).execute()
Step 5: Replacing Placeholders and Updating Document Content
Functions to replace placeholders and find text/table indexes:
def replace_placeholder(document_id, placeholder, replacement_text):
requests = [
{
"replaceAllText": {
"containsText": {"text": placeholder, "matchCase": True},
"replaceText": replacement_text,
}
}
]
docs_service.documents().batchUpdate(
documentId=document_id, body={"requests": requests}
).execute()
def find_table_index(document, placeholder):
for index, element in enumerate(document["body"]["content"]):
if "table" in element:
for row in element["table"]["tableRows"]:
for cell in row["tableCells"]:
for content in cell["content"]:
if "paragraph" in content:
for run in content["paragraph"]["elements"]:
if (
"textRun" in run
and placeholder in run["textRun"]["content"]
):
location_dict = dict()
location_dict["table_index"] = document["body"][
"content"
][index]["startIndex"]
location_dict["content_index"] = run["startIndex"]
return location_dict
return None
def find_text_index(document, placeholder):
for index, element in enumerate(document["body"]["content"]):
if "paragraph" in element:
for run in element["paragraph"]["elements"]:
if "textRun" in run and placeholder in run["textRun"]["content"]:
location_dict = {
"type": "paragraph",
"paragraph_index": element["startIndex"],
"content_index": run["startIndex"],
}
return location_dict
return None
Step 6: Processing the Template
Function to process the template and insert data into the document:
def insert_table_rows(requests, table_index, num_rows):
for _ in range(num_rows):
requests.append(
{
"insertTableRow": {
"tableCellLocation": {
"tableStartLocation": {"index": table_index},
"rowIndex": 0,
"columnIndex": 1,
},
"insertBelow": True,
}
}
)
def process_template(config):
templates = config["templates"]
for template in templates:
new_doc_id = create_document_copy(template["id"], template["title"])
document = docs_service.documents().get(documentId=new_doc_id).execute()
requests = []
for section in template["sections"]:
placeholder = section["placeholder"]
content_type = section["type"]
data = section["data"]
# data = section
if not data:
continue
requests.append(
{
"replaceAllText": {
"containsText": {"text": f"{placeholder}", "matchCase": True},
"replaceText": "",
}
}
)
if content_type == "table":
indices = find_table_index(document, placeholder)
if not indices:
print("couldn't find table index")
continue
table_index = indices["table_index"]
content_index = indices["content_index"]
columns = section["columns"]
padding = 3 # This allows us to go to the next table cell
records = data
insert_table_rows(requests, table_index, len(records))
for column in columns:
print(content_index)
requests.append(
{
"insertText": {
"location": {"index": content_index},
"text": f"{column}",
}
}
)
content_index += len(column) + padding
for record in records:
for column in columns:
print(f"column is {record[column]}")
requests.append(
{
"insertText": {
"location": {"index": content_index},
"text": f"\n{record[column]}",
}
}
)
content_index += len(record[column]) + padding
content_index += 1 #This is to move to the next row in the table
if content_type == "text":
indices = find_text_index(document, placeholder)
paragraph_index = indices["paragraph_index"]
content_index = indices["content_index"]
data_rows = section["data"]
for data in data_rows:
requests.append(
{
"insertText": {
"location": {"index": paragraph_index + 1},
"text": f"\n{data}",
}
}
)
docs_service.documents().batchUpdate(
documentId=new_doc_id, body={"requests": requests}
).execute()
Step 7: Main Function to Execute the Script
The main function to execute the script, make sure you put the right template name here from your files:
def main():
with open("template_project_proposal.json", "r") as config_file:
config = json.load(config_file)
process_template(config)
if __name__ == "__main__":
main()
Example JSON and Document Template
Here's what your templates should look like, we pass your Google Doc's template ID, title, and sections with their respective data/types and columns where needed:
{
"templates": [
{
"id": "1708SM-ym9C8BsdqNrkfQfAcdreS2hXmdoXqiKbQycaaQ",
"title": "Site Tour Proposal",
"sections": [
{
"placeholder": "{{REQUIREMENTS}}",
"type": "table",
"data": [
{
"neighborhood": "Crotona",
"property_types": "educational, community facility, office",
"square feet": "35,000",
"lot_size": "40,000",
"use groups": "3,6"
}
],
"columns": [
"neighborhood",
"property_types",
"square feet",
"lot_size",
"use groups"
]
},
{
"placeholder": "{{SHORT_DESCRIPTION}}",
"type": "text",
"data": [
"Site Selection Proposal for Crotona section of the Bronx"
]
},
{
"placeholder": "{{PROJECT_NAME}}",
"type": "text",
"data": [
"Client Tour Proposal"
]
}
]
}
]
}
And here's what my Doc template looks like with the placeholders:
Some things to consider:
- Google Doc's text indexing is a bit complicated. The official suggestion is to work backwards, because adding text affects the index number of the text that follows. My template is set up to fill the bottom placeholders first.
- Table content - I haven't quite figured this part out, I'm currently structuring the {PLACEHOLDER} with no spaces before or after in its column. Every other column needs to have exactly one space for the script to work. If you're reading this, I'm still working on a better solution. But if you follow this rule for tables, you should be fine.
The final result:
Conclusion
By following these steps, you can automate the generation of documents using Python and Google Docs. I've created Apple Shortcuts that call this script with Contact data to create contact sheets, made tour templates for tenant representation assignments, and much more. This automation will help you streamline your transactions, save time, and reduce errors. Subscribe to RETech.tips for more tutorials and insights on leveraging technology in real estate.