diff --git a/client/galaxy/scripts/bundleEntries.js b/client/galaxy/scripts/bundleEntries.js
index c666d664310..0390749fb83 100644
--- a/client/galaxy/scripts/bundleEntries.js
+++ b/client/galaxy/scripts/bundleEntries.js
@@ -79,6 +79,7 @@ export function multiHistory(options) {
export { default as panels } from "layout/panel";
export { show_in_overlay, hide_modal, show_message, show_modal, Modal } from "layout/modal";
export { make_popupmenu, make_popup_menus } from "ui/popupmenu";
+export { render_embedded_items } from "mvc/embedded-objects";
export { default as async_save_text } from "utils/async-save-text";
// Previously "chart"
diff --git a/client/galaxy/scripts/components/PageEditor/PageEditor.vue b/client/galaxy/scripts/components/PageEditor/PageEditor.vue
new file mode 100644
index 00000000000..f589b15e23a
--- /dev/null
+++ b/client/galaxy/scripts/components/PageEditor/PageEditor.vue
@@ -0,0 +1,38 @@
+
+
+
+
${
- WYMeditor.TOOLS
- } |
${
- // + WYMeditor.HTML
- WYMeditor.IFRAME
- }${WYMeditor.STATUS} |
,
+ tag='pre', attrs=[('class', 'screen')]
+ """
+
+ # If ignoring content, just increment tag count and ignore.
+ if self.ignore_content:
+ self.num_open_tags_for_ignore += 1
+ return
+
+ # Not ignoring tag; look for embedded content.
+ embedded_item = False
+ for attribute in attrs:
+ if (attribute[0] == "class") and ("embedded-item" in attribute[1].split(" ")):
+ embedded_item = True
+ break
+ # For embedded content, set ignore flag to ignore current content and add new content for embedded item.
+ if embedded_item:
+ # Set processing attributes to ignore content.
+ self.ignore_content = True
+ self.num_open_tags_for_ignore = 1
+
+ # Insert content for embedded element.
+ for attribute in attrs:
+ name = attribute[0]
+ if name == "id":
+ # ID has form '-'
+ item_class, item_id = attribute[1].split("-")
+ embed_html = self.render_embed_html_fn(self.trans, item_class, item_id)
+ self.pieces.append(embed_html)
+ return
+
+ # Default behavior: not ignoring and no embedded content.
+ uattrs = []
+ strattrs = ''
+ if attrs:
+ for key, value in attrs:
+ value = value.replace('>', '>').replace('<', '<').replace('"', '"')
+ value = self.bare_ampersand.sub("&", value)
+ uattrs.append((key, value))
+ strattrs = ''.join(' %s="%s"' % (k, v) for k, v in uattrs)
+ if tag in self.elements_no_end_tag:
+ self.pieces.append('<%s%s />' % (tag, strattrs))
+ else:
+ self.pieces.append('<%s%s>' % (tag, strattrs))
+
+ def handle_endtag(self, tag):
+ """
+ Called for each end tag
+
+ E.g. for , tag will be 'pre'
+ """
+ # If ignoring content, see if current tag is the end of content to ignore.
+ if self.ignore_content:
+ self.num_open_tags_for_ignore -= 1
+ if self.num_open_tags_for_ignore == 0:
+ # Done ignoring content.
+ self.ignore_content = False
+ return
+
+ # Default behavior: reconstruct the original end tag.
+ if tag not in self.elements_no_end_tag:
+ self.pieces.append("%s>" % tag)
+
+ def handle_charref(self, ref):
+ # called for each character reference, e.g. for ' ', ref will be '160'
+ # Reconstruct the original character reference.
+ ref = ref.lower()
+ if ref.startswith('x'):
+ value = int(ref[1:], 16)
+ else:
+ value = int(ref)
+
+ if value in _cp1252:
+ self.pieces.append('%s;' % hex(ord(_cp1252[value]))[1:])
+ else:
+ self.pieces.append('%s;' % ref)
+
+ def handle_entityref(self, ref):
+ # called for each entity reference, e.g. for '©', ref will be 'copy'
+ # Reconstruct the original entity reference.
+ if ref in name2codepoint or ref == 'apos':
+ self.pieces.append('&%s;' % ref)
+ else:
+ self.pieces.append('&%s' % ref)
+
+ def handle_data(self, text):
+ """
+ Called for each block of plain text
+
+ Called outside of any tag and not containing any character or entity
+ references. Store the original text verbatim.
+ """
+ if self.ignore_content:
+ return
+ self.pieces.append(text)
+
+ def handle_comment(self, text):
+ # called for each HTML comment, e.g.
+ # Reconstruct the original comment.
+ self.pieces.append('' % text)
+
+ def handle_decl(self, text):
+ # called for the DOCTYPE, if present, e.g.
+ #
+ # Reconstruct original DOCTYPE
+ self.pieces.append('' % text)
+
+ def handle_pi(self, text):
+ # called for each processing instruction, e.g.
+ # Reconstruct original processing instruction.
+ self.pieces.append('%s>' % text)
+
+ def output(self):
+ '''Return processed HTML as a single string'''
+ return ''.join(self.pieces)
+
+
+PAGE_MAXRAW = 10**15
+
+
+def get_page_identifiers(item_id, app):
+ # Assume if item id is integer and less than 10**15, it's unencoded.
+ try:
+ decoded_id = int(item_id)
+ if decoded_id >= PAGE_MAXRAW:
+ raise ValueError("Identifier larger than maximum expected raw int, must be already encoded.")
+ encoded_id = app.security.encode_id(item_id)
+ except ValueError:
+ # It's an encoded id.
+ encoded_id = item_id
+ decoded_id = base.decode_id(app, item_id)
+ return (encoded_id, decoded_id)
+
+
+# Utilities for encoding/decoding HTML content.
+PLACEHOLDER_TEMPLATE = ''''''
+
+# This is a mapping of the id portion of page contents to the cssclass/shortname.
+PAGE_CLASS_MAPPING = {
+ 'History': 'History',
+ 'HistoryDatasetAssociation': 'Dataset',
+ 'StoredWorkflow': 'Workflow',
+ 'Visualization': 'Visualization'
+}
+
+
+def placeholderRenderForEdit(trans, item_class, item_id):
+ return placeholderRenderForSave(trans, item_class, item_id, encode=True)
+
+
+def placeholderRenderForSave(trans, item_class, item_id, encode=False):
+ encoded_item_id, decoded_item_id = get_page_identifiers(item_id, trans.app)
+ item_name = ''
+ if item_class == 'History':
+ history = trans.sa_session.query(trans.model.History).get(decoded_item_id)
+ history = base.security_check(trans, history, False, True)
+ item_name = history.name
+ elif item_class == 'HistoryDatasetAssociation':
+ hda = trans.sa_session.query(trans.model.HistoryDatasetAssociation).get(decoded_item_id)
+ hda_manager = HDAManager(trans.app)
+ hda = hda_manager.get_accessible(decoded_item_id, trans.user)
+ item_name = hda.name
+ elif item_class == 'StoredWorkflow':
+ wf = trans.sa_session.query(trans.model.StoredWorkflow).get(decoded_item_id)
+ wf = base.security_check(trans, wf, False, True)
+ item_name = wf.name
+ elif item_class == 'Visualization':
+ visualization = trans.sa_session.query(trans.model.Visualization).get(decoded_item_id)
+ visualization = base.security_check(trans, visualization, False, True)
+ item_name = visualization.title
+ class_shorthand = PAGE_CLASS_MAPPING[item_class]
+ if encode:
+ item_id = encoded_item_id
+ else:
+ item_id = decoded_item_id
+ return PLACEHOLDER_TEMPLATE.format(
+ item_class=item_class,
+ class_shorthand=class_shorthand,
+ class_shorthand_lower=class_shorthand.lower(),
+ item_id=item_id,
+ item_name=item_name
+ )
diff --git a/lib/galaxy/web/base/controller.py b/lib/galaxy/web/base/controller.py
index da39c15233e..49e209c8f5e 100644
--- a/lib/galaxy/web/base/controller.py
+++ b/lib/galaxy/web/base/controller.py
@@ -2,7 +2,6 @@
Contains functionality needed in every web interface
"""
import logging
-import re
from six import string_types
from sqlalchemy import true
@@ -54,13 +53,6 @@ log = logging.getLogger(__name__)
SUCCESS, INFO, WARNING, ERROR = "done", "info", "warning", "error"
-def _is_valid_slug(slug):
- """ Returns true if slug is valid. """
-
- VALID_SLUG_RE = re.compile(r"^[a-z0-9\-]+$")
- return VALID_SLUG_RE.match(slug)
-
-
class BaseController(object):
"""
Base class for Galaxy web application controllers.
@@ -1118,7 +1110,7 @@ class UsesVisualizationMixin(UsesLibraryMixinItems):
title_err = slug_err = ""
if not title:
title_err = "visualization name is required"
- elif slug and not _is_valid_slug(slug):
+ elif slug and not managers_base.is_valid_slug(slug):
slug_err = "visualization identifier must consist of only lowercase letters, numbers, and the '-' character"
elif slug and trans.sa_session.query(trans.model.Visualization).filter_by(user=user, slug=slug, deleted=False).first():
slug_err = "visualization identifier must be unique"
@@ -1361,7 +1353,7 @@ class SharableMixin(object):
def _is_valid_slug(self, slug):
""" Returns true if slug is valid. """
- return _is_valid_slug(slug)
+ return managers_base.is_valid_slug(slug)
@web.expose
@web.require_login("modify Galaxy items")
diff --git a/lib/galaxy/webapps/galaxy/api/page_revisions.py b/lib/galaxy/webapps/galaxy/api/page_revisions.py
index faf2be37035..f240f6e9155 100644
--- a/lib/galaxy/webapps/galaxy/api/page_revisions.py
+++ b/lib/galaxy/webapps/galaxy/api/page_revisions.py
@@ -3,9 +3,9 @@ API for updating Galaxy Pages
"""
import logging
-from galaxy import exceptions
+from galaxy.managers.base import get_object
+from galaxy.managers.pages import PageManager
from galaxy.model.item_attrs import UsesAnnotations
-from galaxy.util.sanitize_html import sanitize_html
from galaxy.web import expose_api
from galaxy.web.base.controller import (
BaseAPIController,
@@ -18,6 +18,10 @@ log = logging.getLogger(__name__)
class PageRevisionsController(BaseAPIController, SharableItemSecurityMixin, UsesAnnotations, SharableMixin):
+ def __init__(self, app):
+ super(PageRevisionsController, self).__init__(app)
+ self.manager = PageManager(app)
+
@expose_api
def index(self, trans, page_id, **kwd):
"""
@@ -30,13 +34,13 @@ class PageRevisionsController(BaseAPIController, SharableItemSecurityMixin, Uses
:rtype: list
:returns: dictionaries containing different revisions of the page
"""
- page = self._get_page(trans, page_id)
- self._verify_page_ownership(trans, page)
-
- r = trans.sa_session.query(trans.app.model.PageRevision).filter_by(page_id=trans.security.decode_id(page_id))
+ page = get_object(trans, page_id, 'Page', check_ownership=False, check_accessible=True)
+ r = trans.sa_session.query(trans.app.model.PageRevision).filter_by(page_id=page.id)
out = []
for page in r:
- out.append(self.encode_all_ids(trans, page.to_dict(), True))
+ as_dict = self.encode_all_ids(trans, page.to_dict(), True)
+ self.manager.rewrite_content_for_export(trans, as_dict)
+ out.append(as_dict)
return out
@expose_api
@@ -54,42 +58,8 @@ class PageRevisionsController(BaseAPIController, SharableItemSecurityMixin, Uses
:rtype: dictionary
:returns: Dictionary with 'success' or 'error' element to indicate the result of the request
"""
- content = payload.get("content", None)
- if not content:
- raise exceptions.ObjectAttributeMissingException("content undefined or empty")
-
- page = self._get_page(trans, page_id)
- self._verify_page_ownership(trans, page)
-
- if 'title' in payload:
- title = payload['title']
- else:
- title = page.title
-
- content = sanitize_html(content)
-
- page_revision = trans.app.model.PageRevision()
- page_revision.title = title
- page_revision.page = page
- page.latest_revision = page_revision
- page_revision.content = content
-
- # Persist
- session = trans.sa_session
- session.flush()
-
- return page_revision.to_dict(view="element")
-
- def _get_page(self, trans, page_id):
- page = None
- try:
- page = trans.sa_session.query(trans.app.model.Page).get(trans.security.decode_id(page_id))
- except Exception:
- pass
- if not page:
- raise exceptions.ObjectNotFound()
- return page
-
- def _verify_page_ownership(self, trans, page):
- if not self.security_check(trans, page, True, True):
- raise exceptions.ItemOwnershipException()
+ page = get_object(trans, page_id, 'Page', check_ownership=True)
+ page_revision = self.manager.save_new_revision(trans, page, payload)
+ rval = self.encode_all_ids(trans, page_revision.to_dict(view="element"), True)
+ self.manager.rewrite_content_for_export(trans, rval)
+ return rval
diff --git a/lib/galaxy/webapps/galaxy/api/pages.py b/lib/galaxy/webapps/galaxy/api/pages.py
index a5a8ac3d75e..c86090a29f0 100644
--- a/lib/galaxy/webapps/galaxy/api/pages.py
+++ b/lib/galaxy/webapps/galaxy/api/pages.py
@@ -3,13 +3,12 @@ API for updating Galaxy Pages
"""
import logging
-from galaxy import exceptions
+from galaxy.managers.base import get_object
from galaxy.managers.pages import (
PageManager,
PageSerializer
)
from galaxy.model.item_attrs import UsesAnnotations
-from galaxy.util.sanitize_html import sanitize_html
from galaxy.web import expose_api
from galaxy.web.base.controller import (
BaseAPIController,
@@ -81,39 +80,10 @@ class PagesController(BaseAPIController, SharableItemSecurityMixin, UsesAnnotati
:rtype: dict
:returns: Dictionary return of the Page.to_dict call
"""
- user = trans.get_user()
-
- if not payload.get("title", None):
- raise exceptions.ObjectAttributeMissingException("Page name is required")
- elif not payload.get("slug", None):
- raise exceptions.ObjectAttributeMissingException("Page id is required")
- elif not self._is_valid_slug(payload["slug"]):
- raise exceptions.ObjectAttributeInvalidException("Page identifier must consist of only lowercase letters, numbers, and the '-' character")
- elif trans.sa_session.query(trans.app.model.Page).filter_by(user=user, slug=payload["slug"], deleted=False).first():
- raise exceptions.DuplicatedSlugException("Page slug must be unique")
-
- content = payload.get("content", "")
- content = sanitize_html(content)
-
- # Create the new stored page
- page = trans.app.model.Page()
- page.title = payload['title']
- page.slug = payload['slug']
- page_annotation = sanitize_html(payload.get("annotation", ""))
- self.add_item_annotation(trans.sa_session, trans.get_user(), page, page_annotation)
- page.user = user
- # And the first (empty) page revision
- page_revision = trans.app.model.PageRevision()
- page_revision.title = payload['title']
- page_revision.page = page
- page.latest_revision = page_revision
- page_revision.content = content
- # Persist
- session = trans.sa_session
- session.add(page)
- session.flush()
-
+ page = self.manager.create(trans, payload)
rval = self.encode_all_ids(trans, page.to_dict(), True)
+ rval['content'] = page.latest_revision.content
+ self.manager.rewrite_content_for_export(trans, rval)
return rval
@expose_api
@@ -128,7 +98,7 @@ class PagesController(BaseAPIController, SharableItemSecurityMixin, UsesAnnotati
:rtype: dict
:returns: Dictionary with 'success' or 'error' element to indicate the result of the request
"""
- page = self._get_page(trans, id)
+ page = get_object(trans, id, 'Page', check_ownership=True)
# Mark a page as deleted
page.deleted = True
@@ -147,22 +117,8 @@ class PagesController(BaseAPIController, SharableItemSecurityMixin, UsesAnnotati
:rtype: dict
:returns: Dictionary return of the Page.to_dict call with the 'content' field populated by the most recent revision
"""
- page = self._get_page(trans, id)
- self.security_check(trans, page, check_ownership=False, check_accessible=True)
+ page = get_object(trans, id, 'Page', check_ownership=False, check_accessible=True)
rval = self.encode_all_ids(trans, page.to_dict(), True)
rval['content'] = page.latest_revision.content
+ self.manager.rewrite_content_for_export(trans, rval)
return rval
-
- def _get_page(self, trans, id): # Fetches page object and verifies security.
- try:
- page = trans.sa_session.query(trans.app.model.Page).get(trans.security.decode_id(id))
- except Exception:
- page = None
-
- if not page:
- raise exceptions.ObjectNotFound()
-
- if page.user != trans.user and not trans.user_is_admin:
- raise exceptions.ItemOwnershipException()
-
- return page
diff --git a/lib/galaxy/webapps/galaxy/controllers/page.py b/lib/galaxy/webapps/galaxy/controllers/page.py
index 78b00076b42..c5254697430 100644
--- a/lib/galaxy/webapps/galaxy/controllers/page.py
+++ b/lib/galaxy/webapps/galaxy/controllers/page.py
@@ -1,9 +1,5 @@
-import re
-from json import loads
from markupsafe import escape
-from six.moves.html_entities import name2codepoint
-from six.moves.html_parser import HTMLParser
from sqlalchemy import (
and_,
desc,
@@ -16,11 +12,18 @@ from sqlalchemy.orm import (
)
from galaxy import (
- managers,
+ exceptions,
model,
util,
web
)
+from galaxy.managers.hdas import HDAManager
+from galaxy.managers.histories import HistoryManager, HistorySerializer
+from galaxy.managers.pages import (
+ get_page_identifiers,
+ PageContentProcessor,
+ PageManager,
+)
from galaxy.model.item_attrs import UsesItemRatings
from galaxy.util import unicodify
from galaxy.util.sanitize_html import sanitize_html
@@ -40,55 +43,6 @@ from galaxy.web.framework.helpers import (
)
-# Copied from https://github.com/kurtmckee/feedparser
-_cp1252 = {
- 128: u'\u20ac', # euro sign
- 130: u'\u201a', # single low-9 quotation mark
- 131: u'\u0192', # latin small letter f with hook
- 132: u'\u201e', # double low-9 quotation mark
- 133: u'\u2026', # horizontal ellipsis
- 134: u'\u2020', # dagger
- 135: u'\u2021', # double dagger
- 136: u'\u02c6', # modifier letter circumflex accent
- 137: u'\u2030', # per mille sign
- 138: u'\u0160', # latin capital letter s with caron
- 139: u'\u2039', # single left-pointing angle quotation mark
- 140: u'\u0152', # latin capital ligature oe
- 142: u'\u017d', # latin capital letter z with caron
- 145: u'\u2018', # left single quotation mark
- 146: u'\u2019', # right single quotation mark
- 147: u'\u201c', # left double quotation mark
- 148: u'\u201d', # right double quotation mark
- 149: u'\u2022', # bullet
- 150: u'\u2013', # en dash
- 151: u'\u2014', # em dash
- 152: u'\u02dc', # small tilde
- 153: u'\u2122', # trade mark sign
- 154: u'\u0161', # latin small letter s with caron
- 155: u'\u203a', # single right-pointing angle quotation mark
- 156: u'\u0153', # latin small ligature oe
- 158: u'\u017e', # latin small letter z with caron
- 159: u'\u0178', # latin capital letter y with diaeresis
-}
-
-
-PAGE_MAXRAW = 10**15
-
-
-def _get_page_identifiers(item_id, app):
- # Assume if item id is integer and less than 10**15, it's unencoded.
- try:
- decoded_id = int(item_id)
- if decoded_id >= PAGE_MAXRAW:
- raise ValueError("Identifier larger than maximum expected raw int, must be already encoded.")
- encoded_id = app.security.encode_id(item_id)
- except ValueError:
- # It's an encoded id.
- encoded_id = item_id
- decoded_id = app.security.decode_id(item_id)
- return (encoded_id, decoded_id)
-
-
def format_bool(b):
if b:
return "yes"
@@ -300,166 +254,6 @@ class VisualizationSelectionGrid(ItemSelectionGrid):
# Adapted from the _BaseHTMLProcessor class of https://github.com/kurtmckee/feedparser
-class _PageContentProcessor(HTMLParser, object):
- """
- Processes page content to produce HTML that is suitable for display.
- For now, processor renders embedded objects.
- """
- bare_ampersand = re.compile(r"&(?!#\d+;|#x[0-9a-fA-F]+;|\w+;)")
- elements_no_end_tag = set([
- 'area', 'base', 'basefont', 'br', 'col', 'command', 'embed', 'frame',
- 'hr', 'img', 'input', 'isindex', 'keygen', 'link', 'meta', 'param',
- 'source', 'track', 'wbr'
- ])
-
- def __init__(self, trans, render_embed_html_fn):
- HTMLParser.__init__(self)
- self.trans = trans
- self.ignore_content = False
- self.num_open_tags_for_ignore = 0
- self.render_embed_html_fn = render_embed_html_fn
-
- def reset(self):
- self.pieces = []
- HTMLParser.reset(self)
-
- def _shorttag_replace(self, match):
- tag = match.group(1)
- if tag in self.elements_no_end_tag:
- return '<' + tag + ' />'
- else:
- return '<' + tag + '>' + tag + '>'
-
- def feed(self, data):
- data = re.compile(r'\s]+?)\s*/>', self._shorttag_replace, data)
- data = data.replace(''', "'")
- data = data.replace('"', '"')
- HTMLParser.feed(self, data)
- HTMLParser.close(self)
-
- def handle_starttag(self, tag, attrs):
- """
- Called for each start tag
-
- attrs is a list of (attr, value) tuples, e.g. for ,
- tag='pre', attrs=[('class', 'screen')]
- """
-
- # If ignoring content, just increment tag count and ignore.
- if self.ignore_content:
- self.num_open_tags_for_ignore += 1
- return
-
- # Not ignoring tag; look for embedded content.
- embedded_item = False
- for attribute in attrs:
- if (attribute[0] == "class") and ("embedded-item" in attribute[1].split(" ")):
- embedded_item = True
- break
- # For embedded content, set ignore flag to ignore current content and add new content for embedded item.
- if embedded_item:
- # Set processing attributes to ignore content.
- self.ignore_content = True
- self.num_open_tags_for_ignore = 1
-
- # Insert content for embedded element.
- for attribute in attrs:
- name = attribute[0]
- if name == "id":
- # ID has form '-'
- item_class, item_id = attribute[1].split("-")
- embed_html = self.render_embed_html_fn(self.trans, item_class, item_id)
- self.pieces.append(embed_html)
- return
-
- # Default behavior: not ignoring and no embedded content.
- uattrs = []
- strattrs = ''
- if attrs:
- for key, value in attrs:
- value = value.replace('>', '>').replace('<', '<').replace('"', '"')
- value = self.bare_ampersand.sub("&", value)
- uattrs.append((key, value))
- strattrs = ''.join(' %s="%s"' % (k, v) for k, v in uattrs)
- if tag in self.elements_no_end_tag:
- self.pieces.append('<%s%s />' % (tag, strattrs))
- else:
- self.pieces.append('<%s%s>' % (tag, strattrs))
-
- def handle_endtag(self, tag):
- """
- Called for each end tag
-
- E.g. for , tag will be 'pre'
- """
- # If ignoring content, see if current tag is the end of content to ignore.
- if self.ignore_content:
- self.num_open_tags_for_ignore -= 1
- if self.num_open_tags_for_ignore == 0:
- # Done ignoring content.
- self.ignore_content = False
- return
-
- # Default behavior: reconstruct the original end tag.
- if tag not in self.elements_no_end_tag:
- self.pieces.append("%s>" % tag)
-
- def handle_charref(self, ref):
- # called for each character reference, e.g. for ' ', ref will be '160'
- # Reconstruct the original character reference.
- ref = ref.lower()
- if ref.startswith('x'):
- value = int(ref[1:], 16)
- else:
- value = int(ref)
-
- if value in _cp1252:
- self.pieces.append('%s;' % hex(ord(_cp1252[value]))[1:])
- else:
- self.pieces.append('%s;' % ref)
-
- def handle_entityref(self, ref):
- # called for each entity reference, e.g. for '©', ref will be 'copy'
- # Reconstruct the original entity reference.
- if ref in name2codepoint or ref == 'apos':
- self.pieces.append('&%s;' % ref)
- else:
- self.pieces.append('&%s' % ref)
-
- def handle_data(self, text):
- """
- Called for each block of plain text
-
- Called outside of any tag and not containing any character or entity
- references. Store the original text verbatim.
- """
- if self.ignore_content:
- return
- self.pieces.append(text)
-
- def handle_comment(self, text):
- # called for each HTML comment, e.g.
- # Reconstruct the original comment.
- self.pieces.append('' % text)
-
- def handle_decl(self, text):
- # called for the DOCTYPE, if present, e.g.
- #
- # Reconstruct original DOCTYPE
- self.pieces.append('' % text)
-
- def handle_pi(self, text):
- # called for each processing instruction, e.g.
- # Reconstruct original processing instruction.
- self.pieces.append('%s>' % text)
-
- def output(self):
- '''Return processed HTML as a single string'''
- return ''.join(self.pieces)
-
-
class PageController(BaseUIController, SharableMixin,
UsesStoredWorkflowMixin, UsesVisualizationMixin, UsesItemRatings):
@@ -473,9 +267,10 @@ class PageController(BaseUIController, SharableMixin,
def __init__(self, app):
super(PageController, self).__init__(app)
- self.history_manager = managers.histories.HistoryManager(app)
- self.history_serializer = managers.histories.HistorySerializer(self.app)
- self.hda_manager = managers.hdas.HDAManager(app)
+ self.page_manager = PageManager(app)
+ self.history_manager = HistoryManager(app)
+ self.history_serializer = HistorySerializer(self.app)
+ self.hda_manager = HDAManager(app)
@web.expose
@web.json
@@ -541,37 +336,11 @@ class PageController(BaseUIController, SharableMixin,
}]
}
else:
- user = trans.get_user()
- p_title = payload.get('title')
- p_slug = payload.get('slug')
- p_annotation = payload.get('annotation')
- if not p_title:
- return self.message_exception(trans, 'Please provide a page name is required.')
- elif not p_slug:
- return self.message_exception(trans, 'Please provide a unique identifier.')
- elif not self._is_valid_slug(p_slug):
- return self.message_exception(trans, 'Page identifier can only contain lowercase letters, numbers, and dashes (-).')
- elif trans.sa_session.query(model.Page).filter_by(user=user, slug=p_slug, deleted=False).first():
- return self.message_exception(trans, 'Page id must be unique.')
- else:
- # Create the new stored page
- p = model.Page()
- p.title = p_title
- p.slug = p_slug
- p.user = user
- if p_annotation:
- p_annotation = sanitize_html(p_annotation)
- self.add_item_annotation(trans.sa_session, user, p, p_annotation)
- # And the first (empty) page revision
- p_revision = model.PageRevision()
- p_revision.title = p_title
- p_revision.page = p
- p.latest_revision = p_revision
- p_revision.content = ""
- # Persist
- trans.sa_session.add(p)
- trans.sa_session.flush()
- return {'message': 'Page \'%s\' successfully created.' % p.title, 'status': 'success'}
+ try:
+ page = self.page_manager.create(trans, payload)
+ except exceptions.MessageException as e:
+ return self.message_exception(trans, str(e))
+ return {'message': 'Page \'%s\' successfully created.' % page.title, 'status': 'success'}
@web.legacy_expose_api
@web.require_login("edit pages")
@@ -637,11 +406,7 @@ class PageController(BaseUIController, SharableMixin,
id = self.decode_id(id)
page = trans.sa_session.query(model.Page).get(id)
assert page.user == trans.user
- content = page.latest_revision.content
- processor = _PageContentProcessor(trans, _placeholderRenderForEdit)
- processor.feed(content)
- content = unicodify(processor.output(), 'utf-8')
- return trans.fill_template("page/editor.mako", page=page, content=content)
+ return trans.fill_template("page/editor.mako", page=page)
@web.expose
@web.require_login("use Galaxy pages")
@@ -685,57 +450,11 @@ class PageController(BaseUIController, SharableMixin,
@web.expose
@web.require_login()
- def save(self, trans, id, content, annotations):
+ def save(self, trans, id, content):
id = self.decode_id(id)
page = trans.sa_session.query(model.Page).get(id)
assert page.user == trans.user
-
- # Sanitize content
- content = sanitize_html(content)
- processor = _PageContentProcessor(trans, _placeholderRenderForSave)
- processor.feed(content)
- # Output is string, so convert to unicode for saving.
- content = unicodify(processor.output(), 'utf-8')
-
- # Add a new revision to the page with the provided content.
- page_revision = model.PageRevision()
- page_revision.title = page.title
- page_revision.page = page
- page.latest_revision = page_revision
- page_revision.content = content
-
- # Save annotations.
- annotations = loads(annotations)
- for annotation_dict in annotations:
- item_id = self.decode_id(annotation_dict['item_id'])
- item_class = self.get_class(annotation_dict['item_class'])
- item = trans.sa_session.query(item_class).filter_by(id=item_id).first()
- if not item:
- raise RuntimeError("cannot find annotated item")
- text = sanitize_html(annotation_dict['text'])
-
- # Add/update annotation.
- if item_id and item_class and text:
- # Get annotation association.
- annotation_assoc_class = eval("model.%sAnnotationAssociation" % item_class.__name__)
- annotation_assoc = trans.sa_session.query(annotation_assoc_class).filter_by(user=trans.get_user())
- if item_class == model.History.__class__:
- annotation_assoc = annotation_assoc.filter_by(history=item)
- elif item_class == model.HistoryDatasetAssociation.__class__:
- annotation_assoc = annotation_assoc.filter_by(hda=item)
- elif item_class == model.StoredWorkflow.__class__:
- annotation_assoc = annotation_assoc.filter_by(stored_workflow=item)
- elif item_class == model.WorkflowStep.__class__:
- annotation_assoc = annotation_assoc.filter_by(workflow_step=item)
- annotation_assoc = annotation_assoc.first()
- if not annotation_assoc:
- # Create association.
- annotation_assoc = annotation_assoc_class()
- item.annotations.append(annotation_assoc)
- annotation_assoc.user = trans.get_user()
- # Set annotation user text.
- annotation_assoc.annotation = text
- trans.sa_session.flush()
+ self.page_manager.save_new_revision(trans, page, {"content": content})
@web.expose
@web.require_login()
@@ -760,7 +479,7 @@ class PageController(BaseUIController, SharableMixin,
self.security_check(trans, page, False, True)
# Process page content.
- processor = _PageContentProcessor(trans, self._get_embed_html)
+ processor = PageContentProcessor(trans, self._get_embed_html)
processor.feed(page.latest_revision.content)
# Output is string, so convert to unicode for display.
page_content = unicodify(processor.output(), 'utf-8')
@@ -942,7 +661,7 @@ class PageController(BaseUIController, SharableMixin,
def _get_embed_html(self, trans, item_class, item_id):
""" Returns HTML for embedding an item in a page. """
item_class = self.get_class(item_class)
- encoded_id, decoded_id = _get_page_identifiers(item_id, trans.app)
+ encoded_id, decoded_id = get_page_identifiers(item_id, trans.app)
if item_class == model.History:
return self._get_embedded_history_html(trans, decoded_id)
@@ -967,52 +686,3 @@ class PageController(BaseUIController, SharableMixin,
elif item_class == model.Page:
pass
-
-
-PLACEHOLDER_TEMPLATE = ''''''
-
-# This is a mapping of the id portion of page contents to the cssclass/shortname.
-PAGE_CLASS_MAPPING = {
- 'History': 'History',
- 'HistoryDatasetAssociation': 'Dataset',
- 'StoredWorkflow': 'Workflow',
- 'Visualization': 'Visualization'
-}
-
-
-def _placeholderRenderForEdit(trans, item_class, item_id):
- return _placeholderRenderForSave(trans, item_class, item_id, encode=True)
-
-
-def _placeholderRenderForSave(trans, item_class, item_id, encode=False):
- encoded_item_id, decoded_item_id = _get_page_identifiers(item_id, trans.app)
- item_name = ''
- if item_class == 'History':
- history = trans.sa_session.query(trans.model.History).get(decoded_item_id)
- history = managers.base.security_check(trans, history, False, True)
- item_name = history.name
- elif item_class == 'HistoryDatasetAssociation':
- hda = trans.sa_session.query(trans.model.HistoryDatasetAssociation).get(decoded_item_id)
- hda_manager = managers.hdas.HDAManager(trans.app)
- hda = hda_manager.get_accessible(decoded_item_id, trans.user)
- item_name = hda.name
- elif item_class == 'StoredWorkflow':
- wf = trans.sa_session.query(trans.model.StoredWorkflow).get(decoded_item_id)
- wf = managers.base.security_check(trans, wf, False, True)
- item_name = wf.name
- elif item_class == 'Visualization':
- visualization = trans.sa_session.query(trans.model.Visualization).get(decoded_item_id)
- visualization = managers.base.security_check(trans, visualization, False, True)
- item_name = visualization.title
- class_shorthand = PAGE_CLASS_MAPPING[item_class]
- if encode:
- item_id = encoded_item_id
- else:
- item_id = decoded_item_id
- return PLACEHOLDER_TEMPLATE.format(
- item_class=item_class,
- class_shorthand=class_shorthand,
- class_shorthand_lower=class_shorthand.lower(),
- item_id=item_id,
- item_name=item_name
- )
diff --git a/scripts/pages_identifier_conversion.py b/scripts/pages_identifier_conversion.py
index ce914938f21..b9cec401071 100644
--- a/scripts/pages_identifier_conversion.py
+++ b/scripts/pages_identifier_conversion.py
@@ -9,12 +9,12 @@ sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), os.pa
import galaxy
import galaxy.app
import galaxy.config
+from galaxy.managers.pages import PageContentProcessor, placeholderRenderForSave
from galaxy.objectstore import build_object_store_from_config
from galaxy.security.idencoding import IdEncodingHelper
from galaxy.util import unicodify
from galaxy.util.bunch import Bunch
from galaxy.util.script import app_properties_from_args, populate_config_args
-from galaxy.webapps.galaxy.controllers.page import _PageContentProcessor, _placeholderRenderForSave
def main(argv):
@@ -37,7 +37,7 @@ def main(argv):
mock_trans = Bunch(app=Bunch(security=security_helper), model=model, user_is_admin=lambda: True, sa_session=session)
for p in pagerevs:
try:
- processor = _PageContentProcessor(mock_trans, _placeholderRenderForSave)
+ processor = PageContentProcessor(mock_trans, placeholderRenderForSave)
processor.feed(p.content)
newcontent = unicodify(processor.output(), 'utf-8')
if p.content != newcontent:
diff --git a/templates/embed_base.mako b/templates/embed_base.mako
index 1e7c74b2622..163f293392a 100644
--- a/templates/embed_base.mako
+++ b/templates/embed_base.mako
@@ -11,7 +11,6 @@
${self.render_title( item )}