Automating Contentful Photo Publishing with Python
Tags: python, tutorial, webdev, productivity
Published: 2020-07-17 23:15:11

Hi, I'm Max Mitchell, a senior at UIUC studying Computer Engineering. I recently created my [personal website](https://www.maxemitchell.com/) with the images hosted on [Contentful](https://www.contentful.com/), but found it time consuming to upload the images and create new photo collections. To fix this, I created a python script to upload a folder of images and put them in a Contentful photo collection! 

In this tutorial I'll be teaching you how to integrate Contentful with Python in order to **upload a folder of images** from your command line.

## Setting Up Contentful

To start, I'll be assuming that you already have an account and space with Contentful. If you need help with that, [this tutorial](https://www.contentful.com/help/contentful-101/) from Contentful should help get you started.

Once you have an account and space created, you need a **content model** that resembles a photo album. In my example, I call it a "Photo Collection" but the important part is having a *Media field with many files*. You can look at all the fields I have below:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/vxvnh10hpvsh3v60fgqk.PNG)

## Coding Time

First, install the [Contentful Management API](https://github.com/contentful/contentful-management.py) with `sudo pip install contentful_management`.

Next, you need two keys to use the API: your *Contentful Management Token* and *Space ID*. 
* You can create a Management Token by going under Settings > API keys > Content management tokens > Generate personal token. 
* Your Space ID can be found under Settings > General settings.

Let's add these constants and import the library, along with os to make dealing with the filepath to the images easier:
```
import contentful_management
import os
CONTENTFUL_MANAGEMENT_TOKEN = "YOUR_TOKEN"
CONTENTFUL_SPACE_ID = "YOUR_SPACE_ID"
```
Now we need to authenticate Contentful *(note that this assumes we are using the master environment, but can be switched with what you want)*:
```
client = contentful_management.Client(CONTENTFUL_MANAGEMENT_TOKEN)
portfolio_space = client.spaces().find(CONTENTFUL_SPACE_ID)
master_env = portfolio_space.environments().find('master')
```

Next, we need a folder of images to upload. For the sake of simplicity, I'll be hard coding the path to the folder that contains the images I want to upload, but you can look at my final code to see how I handled passing the folder names into the script. *This also assumes you're on windows.*
```
directory = os.fsencode("D:/edited pics/testFolder")
```

> A quick note about how *Contentful handles media uploads*:
> 1. You create an upload.
> 2. You pass a link to this upload into the attributes for a new asset.
> 3. You *process* the asset, which is an asynchronous task on Contentful's back-end. 
> 4. Once it's processed, it can be added to a photo collection and published.

```
photos = []
for file in os.listdir(directory):
   if filename.endswith(".jpg"):
      filename = os.fsdecode(file)
      contentful_upload = portfolio_space.uploads().create(os.fsdecode(filepath))
      asset_attributes = {
         'fields': {
            'title': {
               "en-US": filename
            },
            'file': {
               'en-US': {
                  'fileName': filename,
                  'contentType': 'image/jpeg',
                  'uploadFrom': contentful_upload.to_link().to_json()
               }
            }
         }
      }
      contentful_asset = master_env.assets().create(None, asset_attributes) # None lets API generate ID
      contentful_asset = contentful_asset.process()  # Processing is an async task, so I need to wait to publish it
      photos.append(contentful_asset) # Store an array of assets so we can later publish them all (once they process)
```

**Nice!** This should upload your images, but we still have the inconvenience of having to open up Contentful and add them all to a new photo collection. Let's automate that!

```
photoCollection_attributes = {
   'content_type_id': 'photoCollection',
   'fields': {
      'photos': {
         'en-US': [item.to_link().to_json() for item in photos]
      },
   }
}
new_photoCollection = master_env.entries().create(None, photoCollection_attributes) # None lets API auto generate unique ID
```

The final step is to publish all the images we uploaded! This is *optional*, but I found it very tedious to individually publish them on the Contentful website, so instead I publish each image but wait to manually publish the photo collection myself.

```
for photo in photos:
   master_env.assets().find(photo.id).publish()
```

## Final Thoughts

I wanted this tutorial to be pretty bare bones and show that it is possible to easily integrate Python with Contentful. This also means that *it lacks usability*. That being said, an improved version of this script (with support for uploading to Google Drive) that I actually use regularly to upload my own images is located in the below GitHub Repo:

{% github maxemitchell/photo_manager no-readme %}

If you're interested, I also made a YouTube video covering my thought process and some of the issues I ran into creating my script. Feel free to check it out below!

{% youtube CMcOuXH0hck %}

If you have any issues or questions about this, please feel free to reach out!