diff --git a/pyworkflow/project.py b/pyworkflow/project.py
index 4637a75899..8cedb0dc61 100644
--- a/pyworkflow/project.py
+++ b/pyworkflow/project.py
@@ -360,7 +360,9 @@ def getCurrentProtocolView(self):
return self._protocolViews[viewKey]
- def create(self, runsView=1, readOnly=False, hostsConf=None, protocolsConf=None, chdir=True, workflow=None):
+ def create(self, runsView=1, readOnly=False, hostsConf=None,
+ protocolsConf=None, chdir=True, workflow=None):
+
"""Prepare all required paths and files to create a new project.
Params:
hosts: a list of configuration hosts associated to this projects
diff --git a/pyworkflow/web/app/views_project.py b/pyworkflow/web/app/views_project.py
index 958aedba4e..1c10d4cc53 100644
--- a/pyworkflow/web/app/views_project.py
+++ b/pyworkflow/web/app/views_project.py
@@ -39,7 +39,7 @@
from views_util import loadProject, getResourceCss, getResourceIcon, getResourceJs, \
getServiceManager, PROJECT_NAME, SERVICE_NAME, \
getVarFromRequest, CTX_PROJECT_PATH, CTX_PROJECT_NAME, WORKFLOW, MODE
-
+from workflowarchive.models import Workflow
def projects(request):
from pyworkflow.utils.utils import prettyDate
@@ -66,9 +66,7 @@ def projects(request):
def workflows(request):
-
workflows = getWorkflowsList()
-
# Annotate existence of projects (hashed)
manager = getServiceManager(None)
diff --git a/pyworkflow/web/pages/resources/css/projects_style.css b/pyworkflow/web/pages/resources/css/projects_style.css
index be1ff3916e..0211efc4fd 100644
--- a/pyworkflow/web/pages/resources/css/projects_style.css
+++ b/pyworkflow/web/pages/resources/css/projects_style.css
@@ -25,6 +25,10 @@
letter-spacing: 0.3em;
text-align: center;
}
+.workFlowForm {
+ list-style: none;
+ margin: 10px;
+}
.projList {
list-style: none;
margin: 10px;
@@ -52,6 +56,7 @@
overflow-x: hidden;
width: 100%;
}
+
li:nth-child(odd) {
background-color: #FFF;
padding: 0px 10px;
diff --git a/pyworkflow/web/pages/settings.py b/pyworkflow/web/pages/settings.py
index 0f437b9a82..0cc9203c83 100644
--- a/pyworkflow/web/pages/settings.py
+++ b/pyworkflow/web/pages/settings.py
@@ -12,8 +12,8 @@
WEB_CONF = pwconfig.loadWebConf()
-# DEBUG = True
-DEBUG = WEB_CONF['DEBUG']
+DEBUG = True
+#DEBUG = WEB_CONF['DEBUG']
TEMPLATE_DEBUG = DEBUG
ADMINS = (
@@ -105,8 +105,10 @@
# Additional locations of static files
WS_ROOT = os.path.join(pw.HOME, 'web', 'webtools')
-serviceFolders = [os.path.join(WS_ROOT, f) for f in os.listdir(WS_ROOT) if os.path.isdir(os.path.join(WS_ROOT, f))]
-staticDirs = [os.path.join(pw.HOME, 'web', 'pages')] + serviceFolders
+serviceFolders = [os.path.join(WS_ROOT, f) for f in os.listdir(WS_ROOT)
+ if os.path.isdir(os.path.join(WS_ROOT, f))]
+staticDirs = [os.path.join(pw.HOME, 'web', 'pages')] \
+ + serviceFolders
# Put strings here, like "/home/html/static" or "C:/www/django/static".
# Always use forward slashes, even on Windows.
@@ -123,7 +125,9 @@
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
templateDirs = [os.path.join(DIRECTORY_PROJECT)] + serviceFolders
-TEMPLATE_DIRS = tuple([os.path.join(d, 'templates') for d in templateDirs])
+templateDirs += [os.path.join(pw.HOME, 'web', 'workflowarchive')]
+
+TEMPLATE_DIRS = tuple([os.path.join(d, 'templates') for d in templateDirs])
# TEMPLATE_DIRS = (
# os.path.join(DIRECTORY_PROJECT, 'templates'),
@@ -179,7 +183,9 @@
# 'django.contrib.admindocs',
# 'gunicorn',
'app',
- 'resumable'
+ 'resumable',
+ 'workflowarchive',# workflow archive project
+ 'tastypie',# web services support
]
try:
@@ -235,4 +241,4 @@
EMAIL_HOST = EMAIL_CONF['EMAIL_HOST']
EMAIL_PORT = EMAIL_CONF['EMAIL_PORT']
EMAIL_HOST_USER = EMAIL_CONF['EMAIL_HOST_USER']
-EMAIL_HOST_PASSWORD = EMAIL_CONF['EMAIL_HOST_PASSWORD']
\ No newline at end of file
+EMAIL_HOST_PASSWORD = EMAIL_CONF['EMAIL_HOST_PASSWORD']
diff --git a/pyworkflow/web/pages/urls.py b/pyworkflow/web/pages/urls.py
index c9422a7cb5..010ffbdcfb 100644
--- a/pyworkflow/web/pages/urls.py
+++ b/pyworkflow/web/pages/urls.py
@@ -8,12 +8,12 @@
from pyworkflow.web import app
from pyworkflow.web.app import views_util, views_home, views_showj
from pyworkflow.web.app.views_management import ScipionResumableUploadView
-
+#from pyworkflow.web.workflowarchive import views as views_workflowarchive
admin.autodiscover()
from django.conf import settings
from pyworkflow.web.pages.settings import WS_ROOT, serviceFolders
from django.views.generic import TemplateView
-
+from django.conf.urls import include
# ===============================================================================
# URL ASSOCIATION
# ===============================================================================
@@ -27,7 +27,11 @@
#PROJECT (CONTENT, RUNTABLE AND GRAPH)
url(r'^projects/', 'app.views_project.projects'),
url(r'^create_project/$', 'app.views_project.create_project'),
+ #INFO: ROB next should finish in $?
url(r'^workflows/', 'app.views_project.workflows'),
+ #TODO: ROB: I do not like to put all urls together
+ # I think they should be at their respective applications
+ url(r'^workflowarchive/', include('workflowarchive.urls')),
url(r'^delete_project/$', 'app.views_project.delete_project'),
url(r'^project_content/$', 'app.views_project.project_content'),
url(r'^get_protocols/$', 'app.views_project.get_protocols'),
@@ -89,8 +93,9 @@
#SHOWJ
url(r'^showj/$', app.views_showj.showj), #Load showj web
url(r'^update_session_table/$', 'app.views_showj.updateSessionTable'),
- url(r'^jsmol/$', 'app.views_showj.jsmol'),
- url(r'^get_chimera_html/$', 'app.views_showj.get_chimera_html'),
+ #TODO: ROB the following two modules do not exists!?
+ ####url(r'^jsmol/$', 'app.views_showj.jsmol'),
+ ####url(r'^get_chimera_html/$', 'app.views_showj.get_chimera_html'),
#BROWSER & UPLOAD FILES
url(r'^upload/', 'app.views_management.upload'),
@@ -131,3 +136,7 @@
handler500 = "app.views_util.handle500error"
urlpatterns = patterns(*mainUrls)
+
+#TODO ROB need css files
+from django.contrib.staticfiles.urls import staticfiles_urlpatterns
+urlpatterns += staticfiles_urlpatterns()
diff --git a/pyworkflow/web/workflowarchive/HtmlTest/workflow.json b/pyworkflow/web/workflowarchive/HtmlTest/workflow.json
new file mode 100644
index 0000000000..5e8c9babba
--- /dev/null
+++ b/pyworkflow/web/workflowarchive/HtmlTest/workflow.json
@@ -0,0 +1,72 @@
+[
+ {
+ "object.className": "ProtStress",
+ "object.id": "103",
+ "object.label": "scipion - stress 3",
+ "object.comment": "",
+ "runName": null,
+ "runMode": 0,
+ "noCpu": 0,
+ "noMem": 2,
+ "amountMem": 256,
+ "noIO": 0,
+ "timeout": 60,
+ "delay": 0,
+ "extraParams": " "
+ },
+ {
+ "object.className": "ProtMonitorSystem",
+ "object.id": "200",
+ "object.label": "scipion - system_monitor",
+ "object.comment": "",
+ "runName": null,
+ "runMode": 0,
+ "inputProtocols": [
+ "103"
+ ],
+ "samplingInterval": 1,
+ "cpuAlert": 90.0,
+ "memAlert": 90.0,
+ "swapAlert": 100.0,
+ "monitorTime": 300.0,
+ "doMail": false,
+ "emailFrom": "from@from.fakeadress.com",
+ "emailTo": "to@to.fakeadress.com",
+ "smtp": "smtp.fakeadress.com"
+ },
+ {
+ "object.className": "ProtStress",
+ "object.id": "467",
+ "object.label": "scipion - stress 3 (copy)",
+ "object.comment": "",
+ "runName": null,
+ "runMode": 0,
+ "noCpu": 0,
+ "noMem": 2,
+ "amountMem": 256,
+ "noIO": 0,
+ "timeout": 60,
+ "delay": 0,
+ "extraParams": " "
+ },
+ {
+ "object.className": "ProtMonitorSystem",
+ "object.id": "499",
+ "object.label": "scipion - system_monitor (copy)",
+ "object.comment": "",
+ "runName": null,
+ "runMode": 0,
+ "inputProtocols": [
+ "467"
+ ],
+ "samplingInterval": 1,
+ "cpuAlert": 90.0,
+ "memAlert": 90.0,
+ "swapAlert": 100.0,
+ "monitorTime": 300.0,
+ "doMail": false,
+ "emailFrom": "from@from.fakeadress.com",
+ "emailTo": "to@to.fakeadress.com",
+ "smtp": "smtp.fakeadress.com"
+ }
+]
\ No newline at end of file
diff --git a/pyworkflow/web/workflowarchive/HtmlTest/workflow.py b/pyworkflow/web/workflowarchive/HtmlTest/workflow.py
new file mode 100644
index 0000000000..c5810dbc3f
--- /dev/null
+++ b/pyworkflow/web/workflowarchive/HtmlTest/workflow.py
@@ -0,0 +1,131 @@
+# -*- coding: utf-8 -*-
+from selenium import webdriver
+from selenium.webdriver.common.by import By
+from selenium.webdriver.common.keys import Keys
+from selenium.webdriver.support.ui import Select
+from selenium.common.exceptions import NoSuchElementException
+from selenium.common.exceptions import NoAlertPresentException
+import unittest, time, re
+import string, random
+import tempfile
+
+example_content="""
+
+
+
+
+
+workflow
+
+
+
+
+| workflow |
+
+
+ | open |
+ /workflowarchive/ |
+ |
+
+
+ | type |
+ id=id_workflowName |
+ workflow_random |
+
+
+ | type |
+ id=id_description |
+ Here goes a description of the tests |
+
+
+ | type |
+ id=id_file |
+ /home/roberto/Scipion/scipion-web/pyworkflow/web/workflowarchive/HtmlTest/workflow.json |
+
+
+ | clickAndWait |
+ css=button[type="submit"] |
+ |
+
+
+ | assertText |
+ css=div.workFlowForm |
+ workflow uploaded |
+
+
+
+
+"""
+
+class Workflow(unittest.TestCase):
+ def setUp(self):
+ self.driver = webdriver.Chrome()
+ self.driver.implicitly_wait(30)
+ self.base_url = "http://0.0.0.0:8000"
+ self.verificationErrors = []
+ self.accept_next_alert = True
+ self.workflowName= "wf_" + ''.join(random.choice(string.lowercase) for x in range(16))
+ self.createJsonFile()
+
+ def createJsonFile(self):
+ self.f = tempfile.NamedTemporaryFile(delete=False)
+ self.f.write(example_content)
+ self.f.close()
+
+ def test_workflow(self):
+ #connect and paint upload workflow form
+ driver = self.driver
+ driver.get(self.base_url + "/workflowarchive/")
+
+ #fill in form using a random workflow name
+ driver.find_element_by_id("id_workflowName").clear()
+ driver.find_element_by_id("id_workflowName").send_keys(self.workflowName)
+ driver.find_element_by_id("id_description").clear()
+ driver.find_element_by_id("id_description").send_keys("Here goes a description of the tests")
+ driver.find_element_by_id("id_file").clear()
+ driver.find_element_by_id("id_file").send_keys(self.f.name)
+
+ #wait two second before sending workflow
+ time.sleep(2)
+ driver.find_element_by_css_selector("button[type=\"submit\"]").click()
+
+ # chek that resul contain magic string
+ import re
+ returnedText = driver.find_element_by_css_selector("div.workFlowForm").text
+ text_found = re.search('workflow uploaded', returnedText)
+ self.assertNotEqual(text_found, None)
+ text_found = re.search('hi you', returnedText)
+ self.assertEqual(text_found, None)
+
+ # wait a bit before clossing window
+ time.sleep(3)
+ os.unlink(self.f.name)
+
+
+ def is_element_present(self, how, what):
+ try: self.driver.find_element(by=how, value=what)
+ except NoSuchElementException as e: return False
+ return True
+
+ def is_alert_present(self):
+ try: self.driver.switch_to_alert()
+ except NoAlertPresentException as e: return False
+ return True
+
+ def close_alert_and_get_its_text(self):
+ try:
+ alert = self.driver.switch_to_alert()
+ alert_text = alert.text
+ if self.accept_next_alert:
+ alert.accept()
+ else:
+ alert.dismiss()
+ return alert_text
+ finally: self.accept_next_alert = True
+
+ def tearDown(self):
+ self.driver.quit()
+ self.assertEqual([], self.verificationErrors)
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/pyworkflow/web/workflowarchive/HtmlTest/workflow.selenium b/pyworkflow/web/workflowarchive/HtmlTest/workflow.selenium
new file mode 100644
index 0000000000..250575dca3
--- /dev/null
+++ b/pyworkflow/web/workflowarchive/HtmlTest/workflow.selenium
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+workflow
+
+
+
+
+| workflow |
+
+
+ | open |
+ /workflowarchive/ |
+ |
+
+
+ | type |
+ id=id_workflowName |
+ workflow_random |
+
+
+ | type |
+ id=id_description |
+ Here goes a description of the tests |
+
+
+ | type |
+ id=id_file |
+ /home/roberto/Scipion/scipion-web/pyworkflow/web/workflowarchive/HtmlTest/workflow.json |
+
+
+ | clickAndWait |
+ css=button[type="submit"] |
+ |
+
+
+ | assertText |
+ css=div.workFlowForm |
+ workflow uploaded |
+
+
+
+
+
diff --git a/pyworkflow/web/workflowarchive/HtmlTest/workflow_betagal1.json b/pyworkflow/web/workflowarchive/HtmlTest/workflow_betagal1.json
new file mode 100644
index 0000000000..e5572a69f2
--- /dev/null
+++ b/pyworkflow/web/workflowarchive/HtmlTest/workflow_betagal1.json
@@ -0,0 +1,146 @@
+[
+ {
+ "object.className": "ProtImportMovies",
+ "object.id": "2",
+ "object.label": "import movies",
+ "object.comment": "\n",
+ "runName": null,
+ "runMode": 0,
+ "importFrom": 0,
+ "filesPath": "",
+ "filesPattern": "Falcon*.mrcs",
+ "copyFiles": false,
+ "acquisitionWizard": null,
+ "voltage": 300.0,
+ "sphericalAberration": 2.0,
+ "amplitudeContrast": 0.1,
+ "magnification": 50000,
+ "samplingRateMode": 0,
+ "samplingRate": 3.54,
+ "scannedPixelSize": 7.0,
+ "gainFile": null
+ },
+ {
+ "object.className": "ProtMovieAlignment",
+ "object.id": "40",
+ "object.label": "movie alignment",
+ "object.comment": "\n",
+ "runName": null,
+ "runMode": 0,
+ "cleanMovieData": true,
+ "alignMethod": 0,
+ "alignFrame0": 0,
+ "alignFrameN": 0,
+ "doGPU": false,
+ "GPUCore": 0,
+ "winSize": 150,
+ "sumFrame0": 0,
+ "sumFrameN": 0,
+ "cropOffsetX": 0,
+ "cropOffsetY": 0,
+ "cropDimX": 0,
+ "cropDimY": 0,
+ "binFactor": 1,
+ "extraParams": "",
+ "hostName": "localhost",
+ "numberOfThreads": 4,
+ "numberOfMpi": 1,
+ "inputMovies": "2.__attribute__outputMovies"
+ },
+ {
+ "object.className": "ProtCTFFind",
+ "object.id": "82",
+ "object.label": "ctffind4",
+ "object.comment": "\n",
+ "runName": null,
+ "runMode": 0,
+ "recalculate": false,
+ "sqliteFile": null,
+ "ctfDownFactor": 1.0,
+ "useCftfind4": true,
+ "astigmatism": 100.0,
+ "findPhaseShift": false,
+ "lowRes": 0.05,
+ "highRes": 0.35,
+ "minDefocus": 0.5,
+ "maxDefocus": 4.0,
+ "windowSize": 256,
+ "hostName": "localhost",
+ "numberOfThreads": 4,
+ "numberOfMpi": 1,
+ "inputMicrographs": "40.__attribute__outputMicrographs"
+ },
+ {
+ "object.className": "EmanProtBoxing",
+ "object.id": "369",
+ "object.label": "eman2 - boxer",
+ "object.comment": "",
+ "runName": null,
+ "runMode": 0,
+ "inputMicrographs": "40.__attribute__outputMicrographs"
+ },
+ {
+ "object.className": "ProtUserSubSet",
+ "object.id": "380",
+ "object.label": "3mics",
+ "object.comment": "",
+ "runName": null,
+ "runMode": 0,
+ "other": null,
+ "sqliteFile": "Runs/000082_ProtCTFFind/ctfs_selection.sqlite,",
+ "outputClassName": "SetOfMicrographs",
+ "inputObject": "82.__attribute__outputCTF"
+ },
+ {
+ "object.className": "XmippProtParticlePicking",
+ "object.id": "420",
+ "object.label": "xmipp3 - manual picking",
+ "object.comment": "",
+ "runName": null,
+ "runMode": 0,
+ "memory": 2.0,
+ "inputMicrographs": "40.__attribute__outputMicrographs"
+ },
+ {
+ "object.className": "XmippProtExtractParticles",
+ "object.id": "449",
+ "object.label": "extract 3 mics",
+ "object.comment": "\n",
+ "runName": null,
+ "runMode": 0,
+ "downsampleType": 0,
+ "downFactor": 2.0,
+ "boxSize": 64,
+ "doSort": false,
+ "rejectionMethod": 0,
+ "maxZscore": 3,
+ "percentage": 5,
+ "doRemoveDust": true,
+ "thresholdDust": 3.5,
+ "doInvert": true,
+ "doFlip": false,
+ "doNormalize": true,
+ "normType": 2,
+ "backRadius": -1,
+ "hostName": "localhost",
+ "numberOfThreads": 1,
+ "numberOfMpi": 1,
+ "ctfRelations": "82.__attribute__outputCTF",
+ "inputCoordinates": "123.__attribute__outputCoordinates",
+ "inputMicrographs": "369.outputMicrographs"
+ },
+ {
+ "object.className": "XmippParticlePickingAutomatic",
+ "object.id": "517",
+ "object.label": "xmipp3 - auto-picking",
+ "object.comment": "",
+ "runName": null,
+ "runMode": 0,
+ "micsToPick": 0,
+ "memory": 2.0,
+ "hostName": "localhost",
+ "numberOfThreads": 1,
+ "numberOfMpi": 1,
+ "xmippParticlePicking": "420"
+ }
+]
diff --git a/pyworkflow/web/workflowarchive/HtmlTest/workflow_betagal2.json b/pyworkflow/web/workflowarchive/HtmlTest/workflow_betagal2.json
new file mode 100644
index 0000000000..597411754a
--- /dev/null
+++ b/pyworkflow/web/workflowarchive/HtmlTest/workflow_betagal2.json
@@ -0,0 +1,101 @@
+[
+ {
+ "object.className": "SpiderProtFilter",
+ "object.id": "2070",
+ "object.label": "spider - filter particles",
+ "object.comment": "",
+ "runName": null,
+ "runMode": 0,
+ "filterType": 3,
+ "filterMode": 0,
+ "usePadding": true,
+ "filterRadius": 0.12,
+ "lowFreq": 0.1,
+ "highFreq": 0.5,
+ "temperature": 0.3,
+ "hostName": "localhost",
+ "numberOfThreads": 2,
+ "numberOfMpi": 1
+ },
+ {
+ "object.className": "XmippProtCL2DAlign",
+ "object.id": "2120",
+ "object.label": "xmipp3 - align with cl2d",
+ "object.comment": "",
+ "runName": null,
+ "runMode": 0,
+ "useReferenceImage": false,
+ "maximumShift": 10,
+ "numberOfIterations": 10,
+ "hostName": "localhost",
+ "numberOfThreads": 1,
+ "numberOfMpi": 4,
+ "inputParticles": "2070.__attribute__outputParticles"
+ },
+ {
+ "object.className": "SpiderProtAlignPairwise",
+ "object.id": "2152",
+ "object.label": "spider - align pairwise",
+ "object.comment": "",
+ "runName": null,
+ "runMode": 0,
+ "innerRadius": 5,
+ "outerRadius": 29,
+ "cgOption": 1,
+ "searchRange": 8,
+ "stepSize": 2,
+ "hostName": "localhost",
+ "numberOfThreads": 2,
+ "inputParticles": "2070.__attribute__outputParticles"
+ },
+ {
+ "object.className": "SpiderProtCustomMask",
+ "object.id": "2233",
+ "object.label": "spider - custom mask",
+ "object.comment": "",
+ "runName": null,
+ "runMode": 0,
+ "filterRadius1": 0.1,
+ "sdFactor": 0.9,
+ "filterRadius2": 0.2,
+ "maskThreshold": 0.01,
+ "inputImage": "2152.__attribute__outputAverage"
+ },
+ {
+ "object.className": "SpiderProtCAPCA",
+ "object.id": "2267",
+ "object.label": "spider - capca",
+ "object.comment": "",
+ "runName": null,
+ "runMode": 0,
+ "analysisType": 0,
+ "addConstant": 0.0,
+ "numberOfFactors": 25,
+ "maskType": 1,
+ "radius": -1,
+ "inputParticles": "2152.__attribute__outputParticles",
+ "maskImage": "2233.__attribute__outputMask"
+ },
+ {
+ "object.className": "SpiderProtClassifyDiday",
+ "object.id": "2319",
+ "object.label": "spider - classify diday",
+ "object.comment": "",
+ "runName": null,
+ "runMode": 0,
+ "numberOfFactors": 10,
+ "inputParticles": "2152.__attribute__outputParticles",
+ "pcaFile": "2267.__attribute__imcFile"
+ },
+ {
+ "object.className": "SpiderProtClassifyWard",
+ "object.id": "2346",
+ "object.label": "spider - classify ward",
+ "object.comment": "",
+ "runName": null,
+ "runMode": 0,
+ "numberOfFactors": 10,
+ "inputParticles": "2152.__attribute__outputParticles",
+ "pcaFile": "2267.__attribute__imcFile"
+ }
+]
diff --git a/pyworkflow/web/workflowarchive/__init__.py b/pyworkflow/web/workflowarchive/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/pyworkflow/web/workflowarchive/admin.py b/pyworkflow/web/workflowarchive/admin.py
new file mode 100644
index 0000000000..31657be115
--- /dev/null
+++ b/pyworkflow/web/workflowarchive/admin.py
@@ -0,0 +1,4 @@
+from django.contrib import admin
+from workflowarchive.models import Workflow
+
+admin.site.register(Workflow)
diff --git a/pyworkflow/web/workflowarchive/api.py b/pyworkflow/web/workflowarchive/api.py
new file mode 100644
index 0000000000..923f1fe5fd
--- /dev/null
+++ b/pyworkflow/web/workflowarchive/api.py
@@ -0,0 +1,32 @@
+from tastypie.resources import ModelResource
+from tastypie.constants import ALL
+from tastypie.utils import trailing_slash
+from django.conf.urls import url
+import json
+from collections import Counter
+
+from models import Workflow
+
+
+class WorkflowResource(ModelResource):
+ """allow search in workflow table"""
+ """http://0.0.0.0:8000/workflowarchive/api/workflow/?format=json
+ You should get back a list of Workflow-like objects.
+ http://0.0.0.0:8000/workflowarchive/api/workflow/?format=json&id=1
+ http://127.0.0.1:8000/workflowarchive/api/workflow/?format=json
+ http://127.0.0.1:8000/workflowarchive/api/workflow/1/?format=json
+ http://127.0.0.1:8000/workflowarchive/api/workflow/schema/?format=json
+ http://127.0.0.1:8000/workflowarchive/api/workflow/set/1;3/?format=json
+ http://127.0.0.1:8000/workflowarchive/api/workflow/?uploaded_at__gte=2017-01-01&format=json
+ """
+
+ class Meta:
+ queryset = Workflow.objects.all()
+ resource_name = 'workflow'
+ filtering = {'id': ALL,
+ 'name': ALL,
+ 'slug': ALL,
+ 'uploaded_at': ['exact', 'lt', 'lte', 'gte', 'gt'],
+ }
+ excludes = ['id','name','uploaded_at','description','resource_uri']
+ allowed_methods = ['get']
diff --git a/pyworkflow/web/workflowarchive/batch b/pyworkflow/web/workflowarchive/batch
new file mode 100644
index 0000000000..bdcf13ba26
--- /dev/null
+++ b/pyworkflow/web/workflowarchive/batch
@@ -0,0 +1,14 @@
+webservices
+ install tastypie django-tastypie==0.12.1 (use 1.13 if django >= 1.7)
+selenium tests:
+ install selenium selenium==3.0.2
+ exec files: chromedriver geckodriver
+
+
+Oyther
+ pytz==2016.7 needs to be installed by hand
+
+------
+
+made urls and templates local
+
diff --git a/pyworkflow/web/workflowarchive/forms.py b/pyworkflow/web/workflowarchive/forms.py
new file mode 100644
index 0000000000..3ed46fbaa4
--- /dev/null
+++ b/pyworkflow/web/workflowarchive/forms.py
@@ -0,0 +1,70 @@
+from django import forms
+from .models import Workflow, VERSIONS
+
+#The basic idea is to create two forms; one with the FileField and the other
+# form with the document, description, ... fields.
+#The user views the form with FileField first. Once the user uploads the file
+# and submits the request, I call the other form with initial values read
+# from the file (I can also delete the file at this step).
+
+class WorkflowForm(forms.ModelForm):
+
+ class Meta:
+ model = Workflow
+
+class FileUploadForm(forms.Form):
+
+
+ version = forms.ChoiceField(label="Scipion version", choices=VERSIONS, required=True )
+ workflowName = forms.CharField(max_length=128, required=True)
+ description = forms.CharField(widget=forms.Textarea(), max_length= 512)
+ email = forms.EmailField(label="Contact person (optinal)", max_length=128, required=False )
+ file = forms.FileField(label='Attach workflow file')
+
+ data_dict={}
+
+ def clean_email(self):
+ self.data_dict['email'] = self.cleaned_data["email"]
+
+ def clean_version(self):
+ self.data_dict['version'] = self.cleaned_data["version"]
+
+ def clean_workflowName(self):
+ self.data_dict['name'] = self.cleaned_data["workflowName"]
+
+ def clean_description(self):
+ self.data_dict['description'] = self.cleaned_data["description"]
+
+ def clean_file(self):
+ data = self.cleaned_data["file"]
+ # read and parse the file, create a Python dictionary `data_dict` from it
+
+ self.data_dict['content'] = data.read()
+
+ form = WorkflowForm(self.data_dict)
+ if form.is_valid():
+ # we don't want to put the object to the database on this step
+ #do I need to save this?
+ self.instance = form.save(commit=False)
+ else:
+ # You can use more specific error message here
+ raise forms.ValidationError(form.errors)
+ return data
+
+ def save(self):
+ # We are not overriding the `save` method here because `form.Form` does not have it.
+ # We just add it for convenience.
+ instance = getattr(self, "instance", None)
+ if instance:
+ instance.save()
+ return instance
+ #fields = ('description', 'document', )
+
+ def as_p(self):
+ "Returns this form rendered as HTML s."
+ return self._html_output(
+ normal_row = u'
%(label)s
%(field)s%(help_text)s
',
+ error_row = u'%s',
+ row_ender = '',
+ help_text_html = u' %s',
+ errors_on_separate_row = True)
\ No newline at end of file
diff --git a/pyworkflow/web/workflowarchive/models.py b/pyworkflow/web/workflowarchive/models.py
new file mode 100644
index 0000000000..7da27d1061
--- /dev/null
+++ b/pyworkflow/web/workflowarchive/models.py
@@ -0,0 +1,31 @@
+from django.db import models
+from django.utils.text import slugify
+from tastypie.utils.timezone import now
+
+VERSIONS = (
+ ('0.0','Unknown'),
+ ('1.0', 'V-1.0'),
+ ('1.1', 'V-1.1'),
+)
+
+class Workflow(models.Model):
+ uploaded_at = models.DateTimeField(auto_now_add=True)
+ name = models.CharField(max_length=128, unique=True)
+ description = models.CharField(max_length=512, blank=True)
+ content = models.CharField(max_length=16384)#For secury reasons this can not be unlimited
+ slug = models.SlugField(null=True, blank=True)
+ version = models.CharField(max_length=3, choices=VERSIONS, default='1')
+ email = models.EmailField(max_length=128,blank=True)
+
+ #name is going to work as PK and as part of URL
+ #so we need to sanitize it (remove spaces, "/", etc
+ def save(self, *args, **kwargs):
+ # For automatic slug generation.
+ if not self.slug:
+ self.slug = slugify(self.name)[:50]
+
+ return super(Workflow, self).save(*args, **kwargs)
+
+ def __unicode__(self):
+ return "%s (%d)"%(self.name, self.id)
+
diff --git a/pyworkflow/web/workflowarchive/templates/wa_download.html b/pyworkflow/web/workflowarchive/templates/wa_download.html
new file mode 100644
index 0000000000..a962666b5c
--- /dev/null
+++ b/pyworkflow/web/workflowarchive/templates/wa_download.html
@@ -0,0 +1,6 @@
+{% block title %} {{Workflows}}{% endblock %}
+
+ {{content}}
+
+
+
diff --git a/pyworkflow/web/workflowarchive/templates/wa_model_form_upload.html b/pyworkflow/web/workflowarchive/templates/wa_model_form_upload.html
new file mode 100644
index 0000000000..0c97be7210
--- /dev/null
+++ b/pyworkflow/web/workflowarchive/templates/wa_model_form_upload.html
@@ -0,0 +1,40 @@
+{% extends 'base_grid.html'%}
+{% block content_head %}
+
+
+
+
+{% endblock %}
+
+
+{% block content %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/pyworkflow/web/workflowarchive/templates/wa_workflows.html b/pyworkflow/web/workflowarchive/templates/wa_workflows.html
new file mode 100644
index 0000000000..1539ab2a97
--- /dev/null
+++ b/pyworkflow/web/workflowarchive/templates/wa_workflows.html
@@ -0,0 +1,61 @@
+{% extends 'base_grid.html'%}
+
+{% block title %} - {{Workflows}}{% endblock %}
+
+{% block content_head %}
+
+
+
+
+
+
+{% endblock %}
+
+{% block content_header %} {% include "header.html"%} {% endblock %}
+
+{% block content %}
+
+
+ {% for workflow in workflows %}
+ -
+ {{workflow.name}}
+
+ (view ,
+ download{% if workflow.email.strip %}, [{{ workflow.email }}]{% endif %}{% ifnotequal workflow.version '0.0' %}, V={{ workflow.version }}{% endifnotequal %})
+ {{workflow.description}}
+
+ {% endfor %}
+
+
+{% endblock %}
+
+
+
diff --git a/pyworkflow/web/workflowarchive/tests.py b/pyworkflow/web/workflowarchive/tests.py
new file mode 100644
index 0000000000..fc0d761121
--- /dev/null
+++ b/pyworkflow/web/workflowarchive/tests.py
@@ -0,0 +1,156 @@
+"""
+This file demonstrates writing tests using the unittest module. These will pass
+when you run "manage.py test".
+
+Replace this with more appropriate tests for your application.
+"""
+from django.test import TestCase
+from django.test import Client
+from models import Workflow
+import json, urllib2
+from tastypie.test import ResourceTestCase
+
+example_content="""
+
+
+
+
+
+workflow
+
+
+
+
+| workflow |
+
+
+ | open |
+ /workflowarchive/ |
+ |
+
+
+ | type |
+ id=id_workflowName |
+ workflow_random |
+
+
+ | type |
+ id=id_description |
+ Here goes a description of the tests |
+
+
+ | type |
+ id=id_file |
+ /home/roberto/Scipion/scipion-web/pyworkflow/web/workflowarchive/HtmlTest/workflow.json |
+
+
+ | clickAndWait |
+ css=button[type="submit"] |
+ |
+
+
+ | assertText |
+ css=div.workFlowForm |
+ workflow uploaded |
+
+
+
+
+"""
+
+def deleteWorkflow( name):
+ try:
+ w = Workflow.objects.get(name=name)
+ w.delete()
+ except Workflow.DoesNotExist:
+ pass
+
+def createWorkflow( name, description, content, version='1.0'):
+ try:
+ w = Workflow.objects.get(name=name)
+ except Workflow.DoesNotExist:
+ #w = Workflow(name=name, description=description, content=content, version=version)
+ w = Workflow(name=name, description=description, content=content, version=version)
+ w.save()
+ return w
+
+def findWorkflow( workflowId):
+ try:
+ w = Workflow.objects.get(id=workflowId)
+ except Workflow.DoesNotExist:
+ return None
+ return w
+
+
+class ModelsTest(TestCase):
+
+ def setUp(self):
+ #self.client = Client()
+ self.workflowContent = example_content
+ self.workflowName = u"workflow_1"
+ self.workflowName2 = u"workflow_2"
+ self.workflowDescription = "description for workflow_1"
+ self.workflowDescription2 = "description for workflow_2"
+
+
+ def test_create_workflow(self):
+
+ wIn = createWorkflow(self.workflowName,
+ self.workflowDescription,
+ self.workflowContent)
+
+ wOut = findWorkflow(wIn.id)
+ self.assertEqual(wIn, wOut)
+
+ wIn2 = createWorkflow(self.workflowName2,
+ self.workflowDescription2,
+ self.workflowContent)
+
+ wOut2 = findWorkflow(wIn2.id)
+ self.assertEqual(wIn2, wOut2)
+
+class WorkflowResourceTest(ResourceTestCase, TestCase):
+ """Import this will not query the test data base!!!!"""
+
+ def setUp(self):
+ super(WorkflowResourceTest, self).setUp()
+ self.client = Client()
+ self.workflowContent = example_content
+ self.workflowName = u"workflow_11"
+ self.workflowName2 = u"workflow_22"
+ self.workflowDescription = "description for workflow_11"
+ self.workflowDescription2 = "description for workflow_22"
+ #webservice url
+ #self.url = "http://127.0.0.1:8000"
+ self.api = "/workflowarchive/api/workflow/"#?format=json
+
+ #fill the database
+ deleteWorkflow(self.workflowName)
+ deleteWorkflow(self.workflowName2)
+
+ wIn = createWorkflow(self.workflowName,
+ self.workflowDescription,
+ self.workflowContent)
+
+ wIn2 = createWorkflow(self.workflowName2,
+ self.workflowDescription2,
+ self.workflowContent)
+
+ def test_query_single_object_Api(self):
+ _url = self.api + "1/"
+ resp = self.api_client.get(_url, format='json')
+ _queryResultDic = self.deserialize(resp)
+ self.assertEqual(_queryResultDic['slug'],self.workflowName)
+
+
+
+ def test_query_many_objects_Api(self):
+ _url = self.api + "?slug=%s"%self.workflowName2
+ resp = self.api_client.get(_url, format='json')
+ _queryResultDic = self.deserialize(resp)
+ q = _queryResultDic["objects"][0]
+ self.assertEqual(q['slug'],self.workflowName2)
+
+ #retieve all stored workflows
+ #_url = self.url + self.api + "?name=%s&format=json"%self.workflowName2
+ #print json.load(urllib2.urlopen(_url))
diff --git a/pyworkflow/web/workflowarchive/urls.py b/pyworkflow/web/workflowarchive/urls.py
new file mode 100644
index 0000000000..ef3d61817a
--- /dev/null
+++ b/pyworkflow/web/workflowarchive/urls.py
@@ -0,0 +1,12 @@
+from django.conf.urls import patterns, url, include
+import views
+from api import WorkflowResource
+workflow_resource = WorkflowResource()
+
+urlpatterns = patterns('',
+ url(r'^$', views.archive_workflow, name='archive_workflow'),
+ url(r'^archive/$', views.archive_workflow, name='archive'),
+ url(r'^list/$', views.list_workflow, name='list_workflow'),
+ url(r'download/(?P\d+)/',views.download_workflow,name='download_workflow'),
+ url(r'^api/', include(workflow_resource.urls)),
+)
diff --git a/pyworkflow/web/workflowarchive/views.py b/pyworkflow/web/workflowarchive/views.py
new file mode 100644
index 0000000000..8407e0c3b6
--- /dev/null
+++ b/pyworkflow/web/workflowarchive/views.py
@@ -0,0 +1,55 @@
+# Create your views here.
+from django.http import HttpResponse
+from forms import FileUploadForm
+from django.shortcuts import render
+from app.views_util import loadProject, getResourceCss, getResourceIcon, getResourceJs
+from app.views_base import base_grid
+from models import Workflow
+import json
+import urllib2
+def archive_workflow(request):
+ context = {'projects_css': getResourceCss('projects'),
+ 'project_utils_js': getResourceJs('project_utils'),
+ }
+ if request.method == 'POST':
+ form = FileUploadForm(request.POST, request.FILES)
+ if form.is_valid():
+ print "Valid form, save it"
+ form.save()
+ context['done']=True
+ else:
+ print "non valid"
+ pass
+ context['form']= form
+
+ else:
+ form = FileUploadForm()
+ context['form']= form
+
+ context = base_grid(request, context)
+ return render(request, 'wa_model_form_upload.html', context)
+
+def list_workflow(request):
+
+ workflows = Workflow.objects.all().order_by("-id")
+
+ context = {'projects_css': getResourceCss('projects'),
+ 'project_utils_js': getResourceJs('project_utils'),
+ 'workflows': workflows,
+ 'host':request.META['HTTP_HOST']
+ }
+
+ context = base_grid(request, context)
+ return render(request,'wa_workflows.html', context)
+
+def download_workflow(request,workflow_id=1):
+ _url = request.build_absolute_uri("/workflowarchive/api/workflow/%s/?format=json"%workflow_id)
+ _workflow = json.load(urllib2.urlopen(_url))
+ content = _workflow['content']
+ slugName = _workflow['slug']
+ #context = {'content': content}
+ #return render(request,'wa_download.html', context)
+
+ response = HttpResponse(content, mimetype='application/octet-stream')
+ response['Content-Disposition'] = 'attachment; filename=%s' % (slugName)
+ return response
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000000..32524ad78a
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,23 @@
+backports-abc==0.5
+backports.ssl-match-hostname==3.5.0.1
+bibtexparser==0.5
+certifi==2016.9.26
+Django==1.5.5
+django-resumable==0.1
+django-tastypie==0.12.1
+matplotlib==1.3.1
+mpi4py==1.3.1
+nose==1.3.7
+numpy==1.8.1
+Pillow==2.5.1
+psutil==2.1.1
+pyparsing==2.0.2
+python-dateutil==1.5
+python-magic==0.4.12
+python-mimeparse==1.6.0
+pytz==2016.7
+scipy==0.14.0
+selenium==3.0.2
+singledispatch==3.4.0.3
+six==1.7.3
+tornado==4.4.2