Skip to content

Build an Image Gallery With WebPurify Moderation and Notification

In this blog post, we’ll explore how to create a public image gallery that allows users to upload their own images. This solution is ideal for websites focusing on user-generated content (UGC) because it leverages Cloudinary’s WebPurify moderation add-on service to ensure uploaded images meet content guidelines. This approach encourages community engagement by allowing user contributions and ensures the content meets quality and suitability standards. We’ll cover every step, from setting up the project to handling notifications for image moderation status.

Our public image gallery allows users to upload images with Cloudinary, which are then sent to Cloudinary’s WebPurify add-on for moderation. Users receive real-time notifications about their image’s status: pending, approved, or rejected. Additionally, users can check for rejected images by clicking a button that lists their IDs.

The project is built using Python with Flask, along with SocketIO and a few lines of JavaScript for the toast notifications.

  1. Image upload. Users can upload images to the gallery.
  2. Moderation. Images are moderated using Cloudinary’s WebPurify add-on service.
  3. Real-time notifications. Users are notified of their image’s status via toast notifications.
  4. Rejected images listing. Users can view IDs of rejected images.
  1. If you haven’t yet done so, sign up for Cloudinary here.
  2. Install Cloudinary and the other necessary packages:
pip install flask python-dotenv cloudinary flask_socketio
  1. Configure Cloudinary
  • In your project, create a file called .env containing your API key, API secret and cloud name, as shown in the example. You can find these in your Cloudinary account, by navigating to the Programmable Media dashboard, and clicking on ‘Go to API Keys’.

# Copy and paste your API environment variable.

# =============================================

CLOUDINARY_URL=cloudinary://<api_key>:<api_secret>@<cloud_name>

Code language: PHP (php)
  1. In your project, create a new file called app.py, then copy and paste the following into this file to configure Cloudinary for the project:
from  dotenv  import  load_dotenv
from  flask  import  Flask, render_template, request, redirect, url_for, jsonify
from  flask_socketio  import  SocketIO
from  cloudinary  import  CloudinaryImage
import  cloudinary
import  cloudinary.api
import  cloudinary.uploader

load_dotenv()
app = Flask(__name__)
socketio = SocketIO(app)
config=cloudinary.config(secure=True)
Code language: JavaScript (javascript)
  1. To use WebPurify, you can subscribe to it from the Add-ons menu.

We’ll review the different parts of the code and explain the functionality.

We’ll upload an image from our computer and apply the moderation functionality.

@app.route('/upload', methods=['POST'])
def  upload():
	files_to_upload = request.files.getlist('file')
	for  file_item  in  files_to_upload:
		if  file_item:
			cloudinary.uploader.upload(file_item,
									   folder = "public_gallery",
									   moderation = "webpurify",
									   notification_url = 'your_ngrok_url/status_notification')

	return  redirect(url_for('index'))
Code language: CSS (css)

Using Cloudinary’s upload method, we can define some settings while uploading the file.

The folder part refers to what folder we’d like to upload our images. In the moderation parameter, we have to specify that we’d like to use the WebPurify add-on. To get notifications about the moderation status of an uploaded image, add the notification_url parameter. For this example, we’ll use Ngrok to help us expose the appropriate endpoint running within our Flask app to the internet, so the webhook notification can be handled.

To get all the images from a specific folder, and to apply some transformations, we’ll use the following code:

def  get_transformed_images():
	all_images = cloudinary.Search().expression('folder:public_gallery').sort_by('public_id', 'desc').max_results(10).execute()
	transformed_images = []
	for  item  in  all_images["resources"]:
		public_id = item['public_id']
		cloudinary_image = CloudinaryImage(public_id)
		image_url = cloudinary_image.build_url(transformation=[
			{'width': 400, 'height': 400, 'crop': 'fill'},
			{'fetch_format': 'auto', 'quality': 'auto'}
		])
		transformed_images.append(image_url)
	return  transformed_images
Code language: JavaScript (javascript)

This function searches through our folder called public_library and grabs 10 images from the folder, which are sorted in descending order by the public_id of the images. This means that only 10 images will be displayed on the site from the specified folder.

In the for loop, we’ll apply transformations that define that the images have to be cropped to fill a 400 pixels wide and 400 pixels height square. The 'fetch_format': 'auto' and the 'quality': 'auto' refers to an optimisation method, where the image will be shown to the user in the best format appropriate for the requesting browser, and the quality of the image will be reduced in a non-visible way to decrease the file size.

While uploading an image to Cloudinary using WebPurify, the image has to go through moderation first. You can check the status of the moderation in your Media Library by clicking on ‘Moderation’ in the top menu and changing the view from ‘Manual’ to ‘WebPurify’. To get notifications about the status of the uploaded image, we’ll use Socket.IO to enable communication between the web client and the server, effectively subscribing to real-time notifications via the websocket protocol.

@app.route('/status_notification', methods=['POST'])
def  status_notification():
	data = request.get_json(silent=True)
	if  data  is  None:
		return  jsonify({"status": "No data received"}), 400

# Check for 'moderation' key in data
	if  'moderation'  in  data:
		status = data['moderation'][0].get('status')
		socketio.emit('moderation', {'status': 'pending'})
		
# Check for 'approved' status
	elif (data['moderation_status'] == 'approved'):
		socketio.emit('moderation', {'status': 'approved'})
# Check for 'rejected' status
	else:
		socketio.emit('moderation', {'status': 'rejected'})
		
	return  jsonify({"status": "Received"}), 200
Code language: PHP (php)

In this code, we’ll look through the data to find the status of the uploaded images. First, we’ll identify the pending status in our data. In the elif part of the function, look for the approved status, and in the else part for the rejected status. When any of these three statuses have been found, the server will emit a message via SocketIO, which the client listens for. Based on the emitted events, we can enable the following actions in the browser: SocketIO will trigger the following script in our HTML file:

<script>
// Status notification
	document.addEventListener('DOMContentLoaded', () => {
		const  socket = io();
				socket.on('moderation', (data) => {
			const  toastEl = document.getElementById('liveToast');
			const  toastBody = document.getElementById('toastBody');
			if (data.status === 'pending') {
				toastBody.innerHTML = `<div class="alert alert-info" role="alert">
					'Your photo is pending for moderation'
				  </div>`
			} else  if (data.status === 'approved') {
				toastBody.innerHTML = `<div class="alert alert-success" role="alert">
					'Your photo has been approved'
				  </div>`
			} else  if (data.status === 'rejected') {
				toastBody.innerHTML = `<div class="alert alert-danger" role="alert">
					'Your photo has been rejected'
				  </div>`
			}
			new  bootstrap.Toast(toastEl).show();
		})
	})
</script>
Code language: HTML, XML (xml)

This script brings up the actual notification on our site using Bootstrap’s toast notifications. The code will be activated from our app.py file, using SocketIO.

As soon as the image is uploaded, you’ll get a “Your photo is pending for moderation” message: Pending for moderation

After a few minutes, when the image went through the moderation, the notification will appear again, with either a rejected or an approved message. Approved notification Rejected notification

Once an image is rejected, you can still get some information about it, like listing the IDs of the rejected images. To get the IDs, the following code will be used:

@app.route('/rejected_images', methods=['GET', 'POST'])
def  rejected_images():
	rejected_images = []
	if  request.method == 'POST':
		results = cloudinary.api.resources_by_moderation("webpurify", "rejected")
		for  img  in  results['resources']:
		image_info = img['public_id'].split('/')[-1]
		rejected_images.append(image_info)

	transformed_images = get_transformed_images()
	return  render_template('index.html', transformed_images=transformed_images, rejected_images=rejected_images)
Code language: JavaScript (javascript)

This will check all moderated images, and will give back a list with the image IDs of the rejected ones. From here we can easily add these to our site on request.

Rejected images

Building an image gallery with Cloudinary’s WebPurify moderation and notifications is a good way to protect your UG app from inappropriate images. With this feature, we can ensure that all uploaded images meet our content guidelines. The combination of Flask, SocketIO, and Bootstrap allows us to create an interactive user interface that notifies users of their image status promptly. Whether you’re looking to add moderation to an existing project or build a new image gallery from scratch, we hope this post shows how you can integrate these technologies efficiently. You can find the working demo in this Github repository.

If you found this blog post helpful and want to discuss it in more detail, head over to the Cloudinary Community forum and its associated Discord.

Back to top

Featured Post