Edycja dokumentów biurowych za pomocą Zoho Remote API

W poprzednim artykule opisałem API udostępniane przez zoho.com. Teraz zajmiemy się wykorzystaniem Zoho Remote API do edycji dokumentów w naszej aplikacji webowej napisanej we frameworku Django.

pyCurl i Zoho Remote API

Wykorzystanie Remote API polega na wysłaniu wieloczęściowych (multipart) żądań post z potrzebnymi danymi i zawartością pliku do edycji/podglądu. W Pythonie najlepiej do tego użyć biblioteki pycurl. Oto przykład "otwierający" dokument w edytorze Zoho:
import pycurl

apikey = 'TWOJ KLUCZ API'

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)
# wysyłamy żądanie na podany URL API
c.setopt(c.URL, "http://export.writer.zoho.com/remotedoc.im?apikey=%s&output=editor" % apikey)
# przekazujemy dane
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 otwierający edytor
print t.contents
W przypadku powodzenia API zwróci wklejkę JavaScriptową przekierowującą na edytor dokumentów Zoho. content to treść dokumentu, filename nazwa pliku, id unikalny ID służący do rozpoznawania dokumentu (np. przy zapisie), format określa format zapisu, saveurl to adres URL, na który Zoho wyśle dokument przy zapisie (powyżej oczywiście localhost nie zadziała). Podobnie wygląda wykorzystanie API dla innych aplikacji (obsługujących arkusze i prezentacje). Wszystko ładnie opisano dla każdej aplikacji: Show (prezentacje), Writer i Sheet.

Aplikacja Django - edycja dokumentów

Pełen kod, projekt Django omawianej poniżej aplikacji można pobrać i przetestować samemu. Należy aby w settings.py ustawić swój własny klucz API Zoho, oraz podać ścieżkę do site_media. Do tego syncdb i dodanie paru dokumentów w Panelu Admina
Powyższy fragment kodu z pycurl można wykorzystać w widokach Django do stworzenia aplikacji do edycji dokumentów dostępnych na naszym serwerze. Wykorzystamy taki prosty model zbierający dokumenty biurowe:
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
Dokumenty dzielimy na trzy typy za pomocą doctype, jako że Zoho udostępnia trzy oddzielne aplikacje dla dokumentów biurowych, arkuszy i prezentacji. Mając gotowy model można zabrać się za widoki, zaczynając od widoku listującego wszystkie dokumenty:
#!/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
W przypadku dokumentów (doc, odt i podobnych) Zoho Writer oferuje - podgląd dokumentu, edycję w edytorze w całym oknie, oraz edycję w ramce. Sposób otwarcia dokumentu określany jest przez parametr output, na który wysyłamy "formularz". Oto trzy widoki dla wspomnianych przypadków:
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))
Wszystkie widoki wysyłają POSTa do API Zoho i przekazują do szablonu rezultat. W przypadku view_doc i edit_doc będzie to kod JavaScript, który przekieruje na edytor. W przypadku edit_doc_in_frame zwrócony zostanie identyfikator dokumentu, który można wykorzystać do otworzenia edytora w ramce.
zohoapp3
zohoapp4
Występujący w danych POST parametr saveurl wskazuje adres URL w naszym serwisie, który odbierze dane do zapisu. Edytor wyśle na niego edytowany plik (w podanym formacie) oraz jego ID. Widok Django obsługujący zapis mógłby wyglądać tak:
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')
Widok odbiera dane wysłane POSTem przez edytor, lecz w przypadku publicznego wystawienia takiego widoku - zastosować dodatkowe środki zabezpieczające przez próbami zapisania plików przez źródła trzecie.

Edycja arkuszy i prezentacji

Remote API dla Zoho Sheet i Zoho Show jest prostsze. API pozwala otworzyć plik do edycji, lecz nie zwraca kodu JS, który otworzy edytor, lecz zwraca adres edytora w nagłówku Location odpowiedzi - na który można przekierować użytkownika, lub otworzyć edytor w ramce. Widok obsługujący arkusze wygląda tak:
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 :(')
Obsługa prezentacji różni się tylko innym adresem URL dla API. W powyższym widoku zastosowaliśmy trochę inny kod pycurl - zbierając nagłówki odpowiedzi, a nie jej treść. Z otrzymanych nagłówków wyłuskujemy nagłówek przekierowania.
zohoapp5
zohoapp2

Za pomocą Remote API można zaimplementować obsługę plików z pakietów biurowych. Zaprezentowane API pozwala na edycję plików, jak i odczyt i zapis w różnych formatach (w tym także HTML, PDF dla dokumentów). Za pomocą API Zoho View można także zaimplementować szybki podgląd dla tych plików. Jeżeli interesuje ciebie wykorzystanie logiki biznesowej obsługiwanej przez Zoho Sheet czy Reports to powinieneś zainteresować się Data API (dane muszą być przetrzymywane na serwerze Zoho w tym przypadku).

RkBlog

Programowanie Sieciowe, 3 October 2009

Comment article
Comment article RkBlog main page Search RSS Contact