Permalink
Browse files

Refs #9424 #9608; Added quick preview for sparql queries; improved sp…

…arql edit for versioning
  • Loading branch information...
1 parent a4b8afe commit da83388dfb51cbde9b2f1854b0959bd5991cbfb5 @zotya zotya committed Jan 30, 2013
View
4 docs/HISTORY.txt
@@ -4,7 +4,9 @@ Changelog
2.4-dev - (unreleased)
----------------------
* Upgrade step:
- ZMI > portal_setup > profile "EEA Sparql" > import Diff tool & CMFEditions Repository Tool
+ ZMI > portal_setup > profile "EEA Sparql" > import Skins & Diff tool & CMFEditions Repository Tool
+* Feature: Added quick preview for sparql queries
+ [szabozo0 refs #9608]
* Feature: Data persistence and versioning
- Added versioning for sparql results
- Possibility to select static & live queries
View
16 eea/sparql/browser/configure.zcml
@@ -151,4 +151,20 @@
permission="cmf.ModifyPortalContent"
/>
+ <browser:page
+ name="sparql.quick_preview"
+ for="*"
+ class=".sparql.QuickPreview"
+ attribute="preview"
+ permission="zope2.View"
+ />
+
+ <browser:page
+ name="sparql.related_items"
+ for="*"
+ class=".sparql.Sparql"
+ attribute="relatedItems"
+ permission="zope2.View"
+ />
+
</configure>
View
68 eea/sparql/browser/sparql.py
@@ -6,6 +6,10 @@
from Products.ZSPARQLMethod.Method import interpolate_query_html
from Products.ZSPARQLMethod.Method import map_arg_values
from Products.ZSPARQLMethod.Method import parse_arg_spec
+from Products.ZSPARQLMethod.Method import interpolate_query
+from Products.ZSPARQLMethod.Method import run_with_timeout
+from Products.ZSPARQLMethod.Method import query_and_get_result
+
from eea.sparql.converter.sparql2json import sparql2json
from eea.sparql.converter.sparql2json import sortProperties
from eea.versions import versions
@@ -14,6 +18,7 @@
import json
import urllib2
import contextlib
+import cgi
logger = logging.getLogger('eea.sparql')
@@ -239,6 +244,11 @@ def isDavizInstalled(self):
return has_daviz
+ def relatedItems(self):
+ """ Items what are back related to this query
+ """
+ return json.dumps([[x.title, x.absolute_url()]
+ for x in self.context.getBRefs()])
class SparqlBookmarksFolder(Sparql):
"""SparqlBookmarksFolder view"""
@@ -311,3 +321,61 @@ def __call__(self):
except Exception, err:
logger.exception(err)
return "Sync done"
+
+class QuickPreview(BrowserView):
+ """ Quick Preview For Query
+ """
+
+ def preview(self):
+ """preview"""
+ tmp_query = self.request.get("sparql_query", "")
+ tmp_arg_spec = self.request.get("arg_spec", "")
+ tmp_endpoint = self.request.get("endpoint", "")
+ tmp_timeout = int(self.request.get("timeout", "0"))
+
+ arg_spec = parse_arg_spec(tmp_arg_spec)
+ missing, arg_values = map_arg_values(arg_spec, self.request.form)
+ error = None
+ if missing:
+ error = ""
+ for missing_arg in missing:
+ error = error + "<div>Argument '%s' missing</div>" % missing_arg
+ else:
+ result = {}
+ data = []
+ error = None
+ cooked_query = interpolate_query(tmp_query, arg_values)
+ args = (tmp_endpoint, cooked_query)
+ try:
+ result, error = {}, None
+ result = run_with_timeout(tmp_timeout,
+ query_and_get_result,
+ *args)
+ data = result.get('result')
+ error = error or result.get('exception')
+ except Exception:
+ import traceback
+ error = traceback.format_exc()
+
+ if error:
+ return "<blockquote class='sparql-error'> %s </blockquote>" % error
+
+ result = []
+ result.append(u"<table class='sparql-results'>")
+ result.append(u"<thead>")
+ result.append(u"<tr>")
+ for var_name in data.get('var_names', []):
+ result.append(u"<th> %s </th>" %var_name)
+ result.append(u"</tr>")
+ result.append(u"</thead>")
+ result.append(u"<tbody>")
+ for row in data.get('rows', []):
+ result.append(u"<tr class='row_0'>")
+ for value in row:
+ result.append(u"<td> %s </td>" %cgi.escape(value.n3()))
+ result.append(u"</tr>")
+ result.append(u"</tbody>")
+ result.append(u"</table>")
+ return "\n".join(result)
+
+
View
1 eea/sparql/configure.zcml
@@ -6,6 +6,7 @@
<include file="profiles.zcml" />
<include file="permissions.zcml" />
+ <include file="skins.zcml" />
<include package=".browser" />
<include package=".cache" />
View
33 eea/sparql/content/sparql.py
@@ -68,6 +68,9 @@
allowable_content_types = ('text/plain',),
widget=TextAreaWidget(
+ macro="sparql_textfield_with_preview",
+ helper_js=("sparql_textfield_with_preview.js",),
+ helper_css=("sparql_textfield_with_preview.css",),
label="Query",
),
required=1
@@ -217,21 +220,21 @@ def execute(self, **arg_values):
new_sparql_results = new_sparql_results[0:-3] + "\n"
self.setSparql_results(new_sparql_results)
pr = getToolByName(self, 'portal_repository')
-
- comment = "Result changed"
- comment = comment.encode('utf')
-
- oldSecurityManager = getSecurityManager()
- newSecurityManager(self.REQUEST, SpecialUsers.system)
- try:
- pr.save(obj=self, comment=comment)
- except FileTooLargeToVersionError:
- commands = view.getCommandSet('plone')
- commands.issuePortalMessage(
- """Changes Saved. Versioning for this file
- has been disabled because it is too large.""",
- msgtype="warn")
- setSecurityManager(oldSecurityManager)
+ if self.portal_type in pr.getVersionableContentTypes():
+ comment = "Result changed"
+ comment = comment.encode('utf')
+
+ oldSecurityManager = getSecurityManager()
+ newSecurityManager(self.REQUEST, SpecialUsers.system)
+ try:
+ pr.save(obj=self, comment=comment)
+ except FileTooLargeToVersionError:
+ commands = view.getCommandSet('plone')
+ commands.issuePortalMessage(
+ """Changes Saved. Versioning for this file
+ has been disabled because it is too large.""",
+ msgtype="warn")
+ setSecurityManager(oldSecurityManager)
if new_result.get('exception', None):
self.cached_result['exception'] = new_result['exception']
View
10 eea/sparql/profiles/default/skins.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0"?>
+<object name="portal_skins">
+ <object name="sparql_templates"
+ meta_type="Filesystem Directory View"
+ directory="eea.sparql:skins/sparql_templates"/>
+
+ <skin-path name="*">
+ <layer insert-after="custom" name="sparql_templates"/>
+ </skin-path>
+</object>
View
7 eea/sparql/skins.zcml
@@ -0,0 +1,7 @@
+<configure
+ xmlns:cmf="http://namespaces.zope.org/cmf"
+ i18n_domain="eea">
+
+ <cmf:registerDirectory name="sparql_templates" />
+
+</configure>
View
12 eea/sparql/skins/sparql_templates/sparql_textfield_with_preview.css
@@ -0,0 +1,12 @@
+.sparql-preview-div {
+ background-color:#DDD;
+ float:left;
+}
+
+.sparql-preview-label {
+ font-size:14px;
+}
+
+.sparql-readonly-field {
+ background-color:#DDD;
+}
View
91 eea/sparql/skins/sparql_templates/sparql_textfield_with_preview.js
@@ -0,0 +1,91 @@
+function preview_sparql(){
+ var ajax_data = {
+ "endpoint" : jQuery("#endpoint_url").attr("value"),
+ "timeout" : jQuery("#timeout").attr("value"),
+ "arg_spec" : jQuery("#arg_spec").attr("value"),
+ "sparql_query" : jQuery("#sparql_query").attr("value")
+ };
+ var preview_arguments = jQuery(".sparql-preview-arguments").attr("value");
+ var args_list = preview_arguments.split("&");
+ jQuery.each(args_list, function(idx, arg){
+ ajax_data[arg.split("=")[0]] = arg.split("=")[1];
+ });
+ jQuery.ajax({
+ url:portal_url + "/sparql.quick_preview",
+ type:"POST",
+ data: ajax_data,
+ success:function(data){
+ var sparql_preview = jQuery("<div class='sparql_preview'></div>");
+ jQuery(data).appendTo(sparql_preview);
+ sparql_preview.dialog({
+ title:"Preview for " + jQuery("#title").attr("value"),
+ modal:true,
+ width:'auto',
+ create: function() {
+ $(this).css("maxHeight", 600);
+ $(this).css("maxWidth", 800);
+ }
+ });
+ }
+ });
+}
+
+function sparql_setstatic(){
+ if (jQuery("#sparql_static").attr("checked")){
+ jQuery("#endpoint_url").attr("readonly", true);
+ jQuery("#timeout").attr("disabled", true);
+ jQuery("#arg_spec").attr("readonly", true);
+ jQuery("#sparql_query").attr("readonly", true);
+
+ jQuery("#endpoint_url").addClass("sparql-readonly-field");
+ jQuery("#arg_spec").addClass("sparql-readonly-field");
+ jQuery("#sparql_query").addClass("sparql-readonly-field");
+ }
+ else{
+ jQuery("#endpoint_url").attr("readonly", false);
+ jQuery("#timeout").attr("disabled", false);
+ jQuery("#arg_spec").attr("readonly", false);
+ jQuery("#sparql_query").attr("readonly", false);
+ jQuery(".sparql-readonly-field").removeClass("sparql-readonly-field");
+ }
+}
+
+function check_relations(){
+ if (window.location.href.indexOf("portal_factory") !== -1){
+ return;
+ }
+ jQuery.ajax({
+ url:absolute_url + "/sparql.related_items",
+ type:"GET",
+ success:function(data){
+ var back_rels = JSON.parse(data);
+ if (back_rels.length !== 0){
+ var warningMessage = jQuery(
+ '<dl class="portalMessage">'+
+ '<dt>Warning</dt>'+
+ '<div style="clear:both"></div'+
+ '<dd>' +
+ 'The result of this query is used by:' +
+ '<ul class="sparql-back-relations"></ul>' +
+ 'Modifying the query may cause problems in them.' +
+ '</dd>'+
+ '</dl>');
+ jQuery("#sparql-base-edit").prepend(warningMessage);
+ jQuery.each(back_rels, function(idx, rel){
+ var rel_msg = jQuery(
+ '<li><a href="'+rel[1]+'">'+rel[0]+'</a></li>'
+ );
+ rel_msg.appendTo(".sparql-back-relations");
+ });
+ }
+ }
+ });
+
+// jQuery("<div>XXX</div>").after(".documentFirstHeading");
+}
+jQuery(document).ready(function($) {
+ jQuery(".sparql-query-results-preview").click(preview_sparql);
+ jQuery("#sparql_static").click(sparql_setstatic);
+ sparql_setstatic();
+ check_relations();
+});
View
159 eea/sparql/skins/sparql_templates/sparql_textfield_with_preview.pt
@@ -0,0 +1,159 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:tal="http://xml.zope.org/namespaces/tal"
+ xmlns:metal="http://xml.zope.org/namespaces/metal"
+ xmlns:i18n="http://xml.zope.org/namespaces/i18n"
+ i18n:domain="plone">
+
+ <head><title></title></head>
+
+ <body>
+
+ <!-- TextArea Widgets -->
+
+ <metal:view_macro define-macro="view"
+ tal:define="kssClassesView context/@@kss_field_decorator_view;
+ getKssClasses nocall:kssClassesView/getKssClassesInlineEditable;">
+ <span metal:define-macro="textarea-field-view"
+ tal:define="kss_class python:getKssClasses(fieldName,
+ templateId='widgets/textarea', macro='textarea-field-view');
+ uid context/UID|nothing;"
+ tal:attributes="class kss_class;
+ id string:parent-fieldname-$fieldName-$uid">
+ <span metal:define-slot="inside"
+ tal:replace="accessor">textarea</span>
+ </span>
+ </metal:view_macro>
+
+ <metal:define define-macro="area_edit">
+ <tal:define
+ define="base python:hasattr(value, 'isUnit');
+ binary python:base and value.isBinary() or context.isBinary(fieldName);
+ content python: not not base and value.getRaw() or value;
+ content python: not binary and content or '';
+ content_length python:len(unicode(content, 'utf-8'));
+ append_only widget/append_only|nothing;
+ maxlength widget/maxlength|nothing;
+ tcname string:textCounter_${fieldName};
+ keypress string:textCounter(this, '${tcname}', ${maxlength});">
+
+ <textarea
+ class="blurrable firstToFocus"
+ tal:attributes="name fieldName;
+ id fieldName;
+ cols widget/cols;
+ rows widget/rows;
+ placeholder widget/placeholder|nothing;
+ onkeydown python:test(maxlength, keypress, None);
+ onkeyup python:test(maxlength, keypress, None);"
+ tal:define="content python:not append_only and content or '';"
+ tal:content="content">content</textarea>
+
+ <div tal:condition="maxlength"
+ i18n:translate="label_characters_remaining">
+ <input readonly="readonly"
+ type="text"
+ name=""
+ maxlength="4"
+ size="4"
+ value=""
+ i18n:name="count"
+ tal:define="remaining python:(int(maxlength) - content_length) + content.count('\n')"
+ tal:attributes="name tcname;
+ value remaining;
+ id string:maxlength_${fieldName};" />
+ characters remaining
+ </div>
+
+ <fieldset tal:condition="append_only">
+ <legend i18n:translate=""
+ tal:define="label widget/label"
+ tal:content="string:HISTORY: ${label}">
+ label
+ </legend>
+ <span i18n:translate=""
+ tal:condition="python:(content_length &lt; 333)"
+ tal:content="accessor">content</span>
+ <textarea readonly="readonly" i18n:translate=""
+ tal:condition="python:(content_length &gt;= 333)"
+ tal:content="content"
+ tal:attributes="cols widget/cols;
+ rows widget/rows;">
+ content
+ </textarea>
+ </fieldset>
+
+ </tal:define>
+ </metal:define>
+
+ <metal:define define-macro="area_format">
+ <tal:define
+ define="field_text_format string:${fieldName}_text_format;
+ contentType python:request.get(field_text_format, context.getContentType(fieldName));
+ get_act python:getattr(field, 'getAllowedContentTypes', False);
+ allowable_ct python:get_act and get_act(here) or ('text/plain',);
+ mimetypes python:[t for t in allowable_ct if t.startswith('text/')];
+ contenttype python:hasattr(field, 'getContentType') and field.getContentType(here) or ''">
+
+ <tal:condition condition="python:len(mimetypes) > 1">
+ <div style="text-align: right; margin-right: 0.75em;">
+ <label i18n:translate="label_format">Format</label>
+ <select tal:define="textareaview context/@@at_textarea_widget;
+ selection python:textareaview.getSelected(mimetypes, contenttype)"
+ tal:attributes="id string:${fieldName}_text_format;
+ name string:${fieldName}_text_format;">
+ <option selected="selected"
+ value=""
+ tal:attributes="value contentType"
+ tal:condition="python:contenttype not in mimetypes">
+ (no change)
+ </option>
+ <option tal:repeat="item mimetypes"
+ tal:content="python:textareaview.lookupMime(item)"
+ tal:attributes="value item;
+ selected python:item in selection and 'selected' or None;"
+ />
+ </select>
+ </div>
+ </tal:condition>
+ <tal:condition condition="python:len(mimetypes) == 1">
+ <input type="hidden"
+ name=""
+ value=""
+ tal:attributes="name field_text_format;
+ value python:mimetypes[0]"
+ />
+ </tal:condition>
+ </tal:define>
+ </metal:define>
+
+ <metal:define define-macro="edit">
+ <metal:use use-macro="field_macro | context/widgets/field/macros/edit">
+ <metal:fill fill-slot="widget_body">
+ <metal:block use-macro="context/widgets/textarea/macros/area_edit" />
+ <metal:block use-macro="context/widgets/textarea/macros/area_format" />
+ </metal:fill>
+ </metal:use>
+ <div class="sparql-preview-div field">
+ <script type="text/javascript" tal:content="string: var portal_url = '${context/portal_url}'; var absolute_url = '${context/absolute_url}'">
+ </script>
+
+ <label class="sparql-preview-label">Quick Preview</label><br/>
+ <label class="sparql-preview-arguments-label">Arguments for preview</label><br/>
+ <span class="sparql-preview-arguments-helper formHelper">Arguments for preview should look like: arg1=value1&amp;arg2=value2</span><br/>
+ <input class="sparql-preview-arguments" type="text"> <br/>
+ <input class="sparql-query-results-preview" type="button" value="Preview Query Results"/>
+ </div>
+ </metal:define>
+
+ <metal:define define-macro="search">
+ <metal:use use-macro="context/widgets/field/macros/edit">
+ <metal:fill fill-slot="widget_body">
+
+ <metal:block use-macro="context/widgets/textarea/macros/area_edit" />
+
+ </metal:fill>
+ </metal:use>
+ </metal:define>
+ </body>
+
+</html>
View
6 jslint.log
@@ -0,0 +1,6 @@
+<jslint>
+<file name='./eea/sparql/skins/sparql_templates/sparql_textfield_with_preview.js'>
+</file>
+<file name='./eea/sparql/browser/js/view.js'>
+</file>
+</jslint>

0 comments on commit da83388

Please sign in to comment.