RkBlog

Hardware, programming and astronomy tutorials and reviews.

Using Zoho remote API to edit office documents in a Django app

An introduction to Zoho.com APIs, CloudSQL, and sample usage of Remote API to edit documents, slides and sheets on a Django powered website. Full source code of the app included.

Zoho.com is a set of web applications - for communication, collaboration, and documents management (documents, sheets, presentations/slides). There are two APIs available for those apps - Data API used on documents and data stored on Zoho apps, that you use, and Remote API that is used on documents stored on your server. The Data API allows more collaboration, and getting precise sets of data (look at CloudSQL). Remote API allows you to use Zoho editors (Writer, Sheet, Show and others) to edit documents (doc, odt, ppt, odp, xls, ods and many more) on your site. More on api.wiki.zoho.com.

pyCurl and Zoho Remote API

In this article we will use the Zoho Remo API to implement document edition in a Django powered website. To use a remote API call you have to POST a multi-part request to the API url containing the file content, and required data. In Python the best library for this task is pycurl. Here is an example how to "open" a document in the Zoho editor:
import pycurl

apikey = 'ZOHO API KEY'

class Bodyfetcher:
	def __init__(self):
		self.contents = ''
	
	def body_callback(self, buf):
		self.contents = self.contents + buf

t = Bodyfetcher()
c = pycurl.Curl()
c.setopt(c.POST, 1)
# send the request to the API URL
c.setopt(c.URL, "http://export.writer.zoho.com/remotedoc.im?apikey=%s&output=editor" % apikey)
# set the required data
c.setopt(c.HTTPPOST, [("content", (c.FORM_FILE, "a.odt")), ("filename", 'a.odt'), ("id", '13'), ("format", 'odt'), ("saveurl", 'http://localhost/')])
c.setopt(c.WRITEFUNCTION, t.body_callback)
c.perform()
c.close()

# JS that will show the editor (if displayed in a browser)
print t.contents
On success the API will return a JavaScript code that will open the Zoho Writer editor in the browser with the document. content is the document content, filename is the document filename, id is a unique ID used to identify the file (on saves for example), format - format to which the document will be saved, saveurl - URL of a page on you website that will handle saving new versions of files (of course it won't work for localhost). You will find full docs on the wiki: Show (slides), Writer i Sheet.

Document editing in a Django App

You can download the full app and play with it. Just edit settings.py and set you Zoho API key, site_media, then syncdb and add some documents in the Admin Panel.
To edit documents we have organise them in some way. We can use a Django model like this one:
class Document(models.Model):
	title = models.CharField(max_length=255, verbose_name='Title')
	description = models.TextField(verbose_name='Description')
	file = models.FilePathField(verbose_name='File', blank=True, null=True, path=settings.MEDIA_ROOT + 'documents/')
	doctype = models.CharField(max_length=10, verbose_name='Type', choices=[('doc', 'Document'), ('sheet', 'Sheet'), ('slide', 'Slides')])
	class Meta:
		verbose_name = 'Document'
		verbose_name_plural = 'Documents'

	def __unicode__(self):
		return self.title
We split documents into 3 groups using doctype (as Zoho gives 3 different apps for docs, sheets and slides). When we have a model and some documents in it we can start with views. The first thing we can do is a view that lists documents:
#!/usr/bin/python
import pycurl

from django.shortcuts import render_to_response
from django.conf import settings
from django.template import RequestContext
from django.http import HttpResponseRedirect,HttpResponse
from django.views.generic.list_detail import object_list

from documents.models import *

def show_index(request):
	"""
	List all documents
	"""
	return object_list(
			request,
			Document.objects.all(),
			paginate_by = 50,
			allow_empty = True,
			template_name = 'show_index.html',
			extra_context = {})
zohoapp1
In case of Zoho Writer API we can open the document in read-only preview, in editor, or embed the editor in an iframe on out web page. That output setting is controlled by variable in GET output of the POST url. Here are views for preview, edit and embedded edit:
class Bodyfetcher:
	def __init__(self):
		self.contents = ''
	
	def body_callback(self, buf):
		self.contents = self.contents + buf

def view_doc(request, did):
	"""
	Display the document in read-only
	"""
	doc = Document.objects.get(id=did)
	ext = str(doc.file.split('.')[-1])
	fname = str(doc.file.split('/')[-1])
	t = Bodyfetcher()
	
	c = pycurl.Curl()
	c.setopt(c.POST, 1)
	c.setopt(c.URL, "http://export.writer.zoho.com/remotedoc.im?apikey=%s&output=view" % settings.APIKEY)
	c.setopt(c.HTTPPOST, [("content", (c.FORM_FILE, str(doc.file))), ("filename", fname), ("id", str(doc.id)), ("format", ext)])
	c.setopt(c.VERBOSE, 1)
	c.setopt(c.WRITEFUNCTION, t.body_callback)
	c.perform()
	c.close()
	zoho = t.contents
	
	return render_to_response(
		'view_doc.html',
		{'doc': doc, 'zoho': zoho},
		context_instance=RequestContext(request))


def edit_doc(request, did):
	"""
	Display the document in Zoho editor
	"""
	doc = Document.objects.get(id=did)
	ext = str(doc.file.split('.')[-1])
	fname = str(doc.file.split('/')[-1])
	t = Bodyfetcher()

	c = pycurl.Curl()
	c.setopt(c.POST, 1)
	c.setopt(c.URL, "http://export.writer.zoho.com/remotedoc.im?apikey=%s&output=editor" % settings.APIKEY)
	c.setopt(c.HTTPPOST, [("content", (c.FORM_FILE, str(doc.file))), ("filename", fname), ("id", str(doc.id)), ("format", ext), ("saveurl", 'http://localhost:8080/doc/save_file/')])
	c.setopt(c.WRITEFUNCTION, t.body_callback)
	c.perform()
	c.close()
	zoho = t.contents
	
	return render_to_response(
		'edit_doc.html',
		{'doc': doc, 'zoho': zoho},
		context_instance=RequestContext(request))


def edit_doc_in_frame(request, did):
	"""
	Show the document in editor embeded in an iframe
	"""
	doc = Document.objects.get(id=did)
	ext = str(doc.file.split('.')[-1])
	fname = str(doc.file.split('/')[-1])
	t = Bodyfetcher()

	c = pycurl.Curl()
	c.setopt(c.POST, 1)
	c.setopt(c.URL, "http://export.writer.zoho.com/remotedoc.im?apikey=%s&output=id" % settings.APIKEY)
	c.setopt(c.HTTPPOST, [("content", (c.FORM_FILE, str(doc.file))), ("filename", fname), ("id", str(doc.id)), ("format", ext), ("saveurl", 'http://localhost:8080/doc/save_file/')])
	c.setopt(c.WRITEFUNCTION, t.body_callback)
	c.perform()
	c.close()
	zoho = t.contents
	
	return render_to_response(
		'edit_doc_in_frame.html',
		{'doc': doc, 'zoho': zoho},
		context_instance=RequestContext(request))
All views POST the data to Zoho API and then pass the result to the template. For view_doc and edit_doc the output will be a JS code that will redirect to the Zoho Editor. API for edit_doc_in_frame will return a key which can be used to open the editor in for example iframe.
zohoapp3
zohoapp4
The saveurl POST variable has the URL that will handle file saving from zoho editor. The editor sends the file content, the ID, and the filename. We can use simple Django view to handle the incoming data:
def save_file(request):
	"""
	Save POSTed file
	"""
	if request.POST:
		data = request.POST.copy()
		doc = Document.objects.get(id=data['id'])
		ext = str(doc.file.split('.')[-1])
		fname = str(doc.file.split('/')[-1])
		
		if fname == data['filename']:
			#ok
			# referer check would be recommended here etc.
			print 'saving file'
			content = request.FILES['content'].read()
			f = open(str(doc.file), 'wb')
			f.write(content)
			f.close()
	return HttpResponse('ok')
The view gets the data and if the filename is ok - saves the document.

Sheet and slides

Remote API for Zoho Sheet and Zoho Show is more simple. You can use the editor, but the API won't return the JS code. Instead you will get the editor URL in response headers (Location header). To handle that we have to use pycurl slightly different:
class Headerfetcher:
	def __init__(self):
		self.contents = ''
	
	def body_callback(self, buf):
		self.contents = self.contents + buf


def edit_sheet(request, did, inframe=False):
	"""
	Display the sheet in Zoho editor
	"""
	doc = Document.objects.get(id=did)
	ext = str(doc.file.split('.')[-1])
	fname = str(doc.file.split('/')[-1])
	t = Headerfetcher()
	
	c = pycurl.Curl()
	c.setopt(c.POST, 1)
	c.setopt(c.URL, "http://sheet.zoho.com/remotedoc.im?apikey=%s&output=editor" % settings.APIKEY)
	c.setopt(c.HTTPPOST, [("content", (c.FORM_FILE, str(doc.file))), ("filename", fname), ("id", str(doc.id)), ("format", ext), ("saveurl", 'http://localhost:8080/doc/save_file/')])
	c.setopt(c.HEADERFUNCTION, t.body_callback)
	c.perform()
	c.close()
	
	x = t.contents.split('
')
	for i in x:
		if i.startswith('Location: '):
			zoho = i.replace('Location: ', '').strip()
			if inframe:
				return render_to_response(
					'edit_sheet_in_frame.html',
					{'doc': doc, 'zoho': zoho},
					context_instance=RequestContext(request))
			else:
				return HttpResponseRedirect(zoho)
	
	return HttpResponse('ZohoError :(')
Instead of getting the response body we now collect response headers, and if there is "Location" header - we extract the URL and use it to show the editor in an iframe:
zohoapp5
zohoapp2

Zoho Data API and CloudSQL

The Data API won't let us use the editors on our websites, but it allows us to manipulate files and data stored in the zoho apps we use. The most interesting for app-making is the data stored in Zoho Reports (and Sheets). Using CloudSQL we can query data saved in sheets and process it in out apps (make reports, whatever). You can check Get Started with Zoho CloudSQL in 4 easy steps.

Here is an example CloudSQL usage with the Python library from Zoho, that maps most of the SQL to objects:
from com.adventnet.zoho.client.report.python.ReportClient import ReportClient

APIKEY = 'YOUR KEY'
LOGINNAME = 'YOUR LOGIN'
PASSWORD = 'YOUR PASSWORD'
DATABASENAME = 'DB NAME'
TABLENAME = 'TABLE NAME'



rc = ReportClient(APIKEY)
rc.login(LOGINNAME,PASSWORD)
uri = rc.getURI(LOGINNAME,DATABASENAME,TABLENAME)

export = open('export.csv', 'w')
rc.exportData(uri, 'csv', export)
#rc.exportData(uri, 'csv', export, "Firma = 'Firma A' AND Zysk = 400")
print 'Done'
You can get the database and table name from the Reports app:
zoho1
zoho2
On those screen shots the database is "test", and table "testowy" (is a sheet). As for the Python API - you will find docs in the package. exportData is used to get data from Reports in a selected format. It takes - "link" to the table, the data format, file object and optional - filter parameters. You can also use plain API calling URL with urllib2: Executing a SQL Query. Database is a "database" of reports in Zoho Reports. Table is a sheet in the Reports app (a database table rendered as a sheet). column is a sheet column (and the named as you set the column name in the sheet).
RkBlog

Django web framework tutorials, 3 October 2009,

Comment article