Commit b43adf6c authored by Łukasz Nowak's avatar Łukasz Nowak

Cleanup shacache and shadir restfulness.

Follow the specification of shacache and shadir: PUT is used in shadir,
POST is used in shacache in order to create new content. erp5_web can be used
without any modification to serve fully restfull POST interface.

Create special action for Web Site which reacts on POST type of request and
support it by low level functionality. This action is available in web view
only and affects only Web Sites configured for shacache.

Other changes:

 * do not check uploaded content, just return calculated sha
 * break compatibility during upload (better now then later)
 * avoid hiding errors during publishing content by hiding exceptions in
   WebSite_publishDocumentByActivity
 * do randomisation in tests and make them live test safe
 * connect to running web server in tests in order to use all stacks during
   uploading and downloading content
 * cleanup erp5_web_shacache and erp5_web_shadir business templates and remove
   not needed scripts
 * during testing treat running site as blackbox and do not try to be too smart
parent aa798bfd
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ActionInformation" module="Products.CMFCore.ActionInformation"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>action</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_web_view</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_web_view</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>web_post_shacache_view</string> </value>
</item>
<item>
<key> <string>permissions</string> </key>
<value>
<tuple>
<string>View</string>
</tuple>
</value>
</item>
<item>
<key> <string>priority</string> </key>
<value> <float>0.8</float> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Web Post Shacache View</string> </value>
</item>
<item>
<key> <string>visible</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>string:${object_url}/WebSite_viewAsWebPost</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>python: context.getSkinSelectionName() == \'SHACACHE\' and request.method == \'POST\' and object is not None and object.isWebMode() and not object.isEditableMode()</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -63,52 +63,6 @@ def WebSection_getDocumentValue(self, key, portal=None, language=None,\
return None
def WebSection_setObject(self, id, ob, **kw):
"""
Add any change of the file uploaded.
"""
sha512sum = hashlib.sha512()
self.REQUEST._file.seek(0)
while True:
d = self.REQUEST._file.read(1<<20)
if not d:
break
sha512sum.update(d)
reference = sha512sum.hexdigest()
if reference != id:
raise ValueError('The content does not match with sha512sum provided.')
# Set object properties
ob.setContentType('application/octet-stream')
ob.setFilename(id)
ob.setReference(reference)
return ob
def WebSection_putFactory(self, name, typ, body):
"""
API SHACACHE
- PUT /<key>
+ parameters required:
* data: it is the file content
The key is the file name.
"""
portal = self.getPortalObject()
document = portal.portal_contributions.newContent(data=body,
filename=name,
discover_metadata=False)
# We can only change the state of the object after all the activities and
# interaction workflow, to avoid any security problem.
document.activate(after_path_and_method_id=(document.getPath(), \
('convertToBaseFormat', 'Document_tryToConvertToBaseFormat', \
'immediateReindexObject', 'recursiveImmediateReindexObject')))\
.WebSite_publishDocumentByActivity()
return document
def File_viewAsWeb(self):
"""
Make possible to send the file data to the client without consume the
......@@ -147,3 +101,28 @@ def File_viewAsWeb(self):
data=next_data
return ''
def WebSite_viewAsWebPost(self, *args, **kwargs):
portal = self.getPortalObject()
sha512sum = hashlib.sha512()
self.REQUEST._file.seek(0)
while True:
d = self.REQUEST._file.read(1<<20)
if not d:
break
sha512sum.update(d)
sha512sum = sha512sum.hexdigest()
document = portal.portal_contributions.newContent(data=self.REQUEST.BODY,
filename='shacache', discover_metadata=False, reference=sha512sum,
content_type='application/octet-stream')
# We can only change the state of the object after all the activities and
# interaction workflow, to avoid any security problem.
document.activate(after_path_and_method_id=(document.getPath(), \
('convertToBaseFormat', 'Document_tryToConvertToBaseFormat', \
'immediateReindexObject', 'recursiveImmediateReindexObject')))\
.publish()
self.REQUEST.RESPONSE.setStatus(201)
return sha512sum
......@@ -7,10 +7,6 @@
<skin_folder>erp5_web_shacache</skin_folder>
<skin_selection>SHACACHE</skin_selection>
</skin_folder_selection>
<skin_folder_selection>
<skin_folder>erp5_web_shacache_core</skin_folder>
<skin_selection>View,SHACACHE</skin_selection>
</skin_folder_selection>
<skin_folder_selection>
<skin_folder>erp5_xhtml_style</skin_folder>
<skin_selection>SHACACHE</skin_selection>
......
......@@ -8,7 +8,7 @@
<dictionary>
<item>
<key> <string>_function</string> </key>
<value> <string>WebSection_setObject</string> </value>
<value> <string>WebSite_viewAsWebPost</string> </value>
</item>
<item>
<key> <string>_module</string> </key>
......@@ -16,7 +16,7 @@
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Base_setObject</string> </value>
<value> <string>WebSite_viewAsWebPost</string> </value>
</item>
<item>
<key> <string>title</string> </key>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_body</string> </key>
<value> <string>portal = context.getPortalObject()\n
if portal.portal_workflow.isTransitionPossible(context, \'publish\'):\n
context.publish()\n
</string> </value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>WebSite_publishDocumentByActivity</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -28,8 +28,9 @@
##############################################################################
import base64
import hashlib
import random
class ShaCacheMixin(object):
"""
......@@ -56,21 +57,15 @@ class ShaCacheMixin(object):
"""
self.login()
self.portal = self.getPortal()
module = self.portal.web_site_module
shacache = getattr(module, 'shacache', None)
if shacache is None:
shacache = module.newContent(portal_type='Web Site',
id='shacache',
title='SHA Cache Server',
skin_selection_name='SHACACHE')
isTransitionPossible = self.portal.portal_workflow.isTransitionPossible
if isTransitionPossible(shacache, 'publish'):
shacache.publish()
self.stepTic()
self.shacache = shacache
self.data = 'Random Content. %s' % self.portal.Base_generateRandomString()
self.shacache = module.newContent(portal_type='Web Site',
title='SHA Cache Server', skin_selection_name='SHACACHE')
self.shacache.publish()
self.header_dict = {
'Content-Type': 'application/json',
'Authorization': 'Basic %s' % (base64.encodestring('ERP5TypeTestCase:'))
}
self.shacache_url = self.shacache.absolute_url()
self.stepTic()
self.data = 'Random Content. %s' % str(random.random())
self.key = hashlib.sha512(self.data).hexdigest()
......@@ -30,7 +30,7 @@
import transaction
import httplib
from StringIO import StringIO
import urlparse
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
from ShaCacheMixin import ShaCacheMixin
......@@ -46,38 +46,19 @@ class TestShaCache(ShaCacheMixin, ERP5TypeTestCase):
"""
return "SHACACHE - HTTP File Cache Server"
def beforeTearDown(self):
"""
Clear everything for next test.
"""
for module in ('document_module',):
folder = self.portal[module]
folder.manage_delObjects(list(folder.objectIds()))
transaction.commit()
self.tic()
def putFile(self, key=None):
def postFile(self, key=None):
"""
Post the file
"""
if key is None:
key = self.key
parsed = urlparse.urlparse(self.shacache_url)
connection = httplib.HTTPConnection(parsed.hostname, parsed.port)
try:
data_file = StringIO()
data_file.write(self.data)
data_file.seek(0)
self.portal.changeSkin('SHACACHE')
path = self.shacache.getPath()
response = self.publish('%s/%s' % (path, key),
request_method='PUT',
stdin=data_file,
basic='ERP5TypeTestCase:')
self.stepTic()
self.assertEqual(response.getStatus(), httplib.CREATED)
connection.request('POST', parsed.path, self.data, self.header_dict)
result = connection.getresponse()
data = result.read()
finally:
data_file.close()
connection.close()
return result.status, data
def getFile(self, key=None):
"""
......@@ -87,18 +68,30 @@ class TestShaCache(ShaCacheMixin, ERP5TypeTestCase):
if key is None:
key = self.key
self.portal.changeSkin('SHACACHE')
self.shacache.REQUEST.set('method', 'GET')
return self.shacache.WebSection_getDocumentValue(key)
parsed = urlparse.urlparse(self.shacache_url)
connection = httplib.HTTPConnection(parsed.hostname, parsed.port)
try:
connection.request('GET', '/'.join([parsed.path, key]), None,
self.header_dict)
result = connection.getresponse()
data = result.read()
finally:
connection.close()
return result.status, data
def test_put_file(self):
"""
Check if the PUT method is creating an object.
"""
self.putFile()
self.assertEquals(1, len(self.portal.document_module))
result, data = self.postFile()
self.assertEqual(result, httplib.CREATED)
self.assertEqual(data, self.key)
transaction.commit()
self.tic()
document = self.portal.document_module.contentValues()[0]
document = self.portal.portal_catalog.getResultValue(reference=self.key)
self.assertNotEqual(None, document)
self.assertEquals(self.key, document.getTitle())
self.assertEquals(self.key, document.getReference())
self.assertEquals(self.data, document.getData())
......@@ -109,29 +102,39 @@ class TestShaCache(ShaCacheMixin, ERP5TypeTestCase):
"""
Check if the file returned is the correct.
"""
self.test_put_file()
self.assertEquals(1, len(self.portal.document_module))
result, data = self.postFile()
self.assertEqual(result, httplib.CREATED)
self.assertEqual(data, self.key)
transaction.commit()
self.tic()
document = self.getFile()
self.assertNotEquals(None, document)
document = self.portal.portal_catalog.getResultValue(reference=self.key)
self.assertNotEqual(None, document)
self.assertEquals(self.data, document.getData())
result, data = self.getFile()
self.assertEqual(result, httplib.OK)
self.assertEquals(data, self.data)
def test_put_file_twice(self):
"""
Check if is allowed to put the same file twice.
"""
self.putFile()
self.assertEquals(1, len(self.portal.document_module))
document = self.portal.document_module.contentValues()[0]
self.postFile()
transaction.commit()
self.tic()
document = self.portal.portal_catalog.getResultValue(reference=self.key)
self.assertEquals('Published', document.getValidationStateTitle())
self.putFile()
self.assertEquals(2, len(self.portal.document_module))
self.postFile()
transaction.commit()
self.tic()
self.assertEquals(2, self.portal.portal_catalog.countResults(
reference=self.key)[0][0])
document2 = self.portal.document_module.contentValues()[1]
document2 = self.portal.portal_catalog.getResultValue(reference=self.key,
sort_on=(('uid', 'ASC'),))
self.assertEquals('Published', document2.getValidationStateTitle())
self.assertEquals('Archived', document.getValidationStateTitle())
self.assertEquals('archived', document.getValidationState())
47
\ No newline at end of file
59
\ No newline at end of file
Web Site | web_post_shacache_view
\ No newline at end of file
erp5_web_download_theme | SHACACHE
erp5_web_shacache | SHACACHE
erp5_web_shacache_core | SHACACHE
erp5_web_shacache_core | View
erp5_xhtml_style | SHACACHE
\ No newline at end of file
erp5_web_shacache
erp5_web_shacache_core
\ No newline at end of file
erp5_web_shacache
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ActionInformation" module="Products.CMFCore.ActionInformation"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>action</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_web_view</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_web_view</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>web_post_shadir_view</string> </value>
</item>
<item>
<key> <string>permissions</string> </key>
<value>
<tuple>
<string>View</string>
</tuple>
</value>
</item>
<item>
<key> <string>priority</string> </key>
<value> <float>0.8</float> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Web Post Shacache View</string> </value>
</item>
<item>
<key> <string>visible</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>string:${object_url}/WebSite_viewAsWebPost</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>python: context.getSkinSelectionName() == \'SHADIR\' and request.method == \'POST\' and object is not None and object.isWebMode() and not object.isEditableMode()</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -104,3 +104,28 @@ def WebSection_setObject(self, id, ob, **kw):
if expiration_date is not None:
ob.setExpirationDate(expiration_date)
return ob
def WebSection_putFactory(self, name, typ, body):
"""
API SHACACHE
- PUT /<key>
+ parameters required:
* data: it is the file content
The key is the file name.
"""
portal = self.getPortalObject()
if name is None:
name = 'shacache'
document = portal.portal_contributions.newContent(data=body,
filename=name,
discover_metadata=False)
# We can only change the state of the object after all the activities and
# interaction workflow, to avoid any security problem.
document.activate(after_path_and_method_id=(document.getPath(), \
('convertToBaseFormat', 'Document_tryToConvertToBaseFormat', \
'immediateReindexObject', 'recursiveImmediateReindexObject')))\
.publish()
return document
......@@ -3,10 +3,6 @@
<skin_folder>erp5_web_download_theme</skin_folder>
<skin_selection>SHADIR</skin_selection>
</skin_folder_selection>
<skin_folder_selection>
<skin_folder>erp5_web_shacache_core</skin_folder>
<skin_selection>SHADIR</skin_selection>
</skin_folder_selection>
<skin_folder_selection>
<skin_folder>erp5_web_shadir</skin_folder>
<skin_selection>SHADIR</skin_selection>
......
......@@ -12,7 +12,7 @@
</item>
<item>
<key> <string>_module</string> </key>
<value> <string>ShaCache</string> </value>
<value> <string>ShaDir</string> </value>
</item>
<item>
<key> <string>id</string> </key>
......
......@@ -2,25 +2,21 @@
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Folder" module="OFS.Folder"/>
<global name="ExternalMethod" module="Products.ExternalMethod.ExternalMethod"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_local_properties</string> </key>
<value>
<tuple/>
</value>
<key> <string>_function</string> </key>
<value> <string>WebSite_viewAsWebPost</string> </value>
</item>
<item>
<key> <string>_objects</string> </key>
<value>
<tuple/>
</value>
<key> <string>_module</string> </key>
<value> <string>ShaDir</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>erp5_web_shacache_core</string> </value>
<value> <string>WebSite_viewAsWebPost</string> </value>
</item>
<item>
<key> <string>title</string> </key>
......
......@@ -27,10 +27,11 @@
#
##############################################################################
import base64
import hashlib
import json
import platform
import random
from DateTime import DateTime
......@@ -64,7 +65,7 @@ class ShaDirMixin(object):
self.login()