Beispiel #1
0
    def get_add_custom_tag(self):
        """Return the page for the popup used to add a custom HTML tag."""
        tag_name = self.request.get('tag_name')

        tag_bindings = tags.get_tag_bindings()

        select_data = []
        for name in tag_bindings.keys():
            clazz = tag_bindings[name]
            select_data.append((name, '%s: %s' % (
                clazz.vendor(), clazz.name())))
        select_data = sorted(select_data, key=lambda pair: pair[1])

        if tag_name:
            tag_class = tag_bindings[tag_name]
        else:
            tag_class = tag_bindings[select_data[0][0]]
        tag_schema = tag_class().get_schema(self)

        schema = schema_fields.FieldRegistry('Add a Component')
        type_select = schema.add_sub_registry('type', 'Component Type')
        type_select.add_property(schema_fields.SchemaField(
            'tag', 'Name', 'string', select_data=select_data))
        schema.add_sub_registry('attributes', registry=tag_schema)

        template_values = {}
        template_values['form_html'] = ObjectEditor.get_html_for(
            self, schema.get_json_schema(), schema.get_schema_dict(), None,
            None, None, required_modules=tag_class.required_modules(),
            extra_js_files=['add_custom_tag.js'])
        self.response.out.write(
            self.get_template('popup.html', []).render(template_values))
Beispiel #2
0
 def _render_custom_tags(self):
     tag_content = safe_dom.NodeList()
     tag_content.append(
         safe_dom.Element('h3').add_text('Custom Tags'))
     ol = safe_dom.Element('ol')
     tag_content.append(ol)
     tag_bindings = tags.get_tag_bindings()
     for name in sorted(tag_bindings.keys()):
         clazz = tag_bindings.get(name)
         tag = clazz()
         vendor = tag.vendor()
         ol.add_child(safe_dom.Element('li').add_text(
             '%s: %s: %s' % (name, tag.__class__.__name__, vendor)))
     return tag_content
Beispiel #3
0
 def _render_custom_tags(self):
     tag_content = safe_dom.NodeList()
     tag_content.append(
         safe_dom.Element('h3').add_text('Custom Tags'))
     ol = safe_dom.Element('ol')
     tag_content.append(ol)
     tag_bindings = tags.get_tag_bindings()
     for name in sorted(tag_bindings.keys()):
         clazz = tag_bindings.get(name)
         tag = clazz()
         vendor = tag.vendor()
         ol.add_child(safe_dom.Element('li').add_text(
             '%s: %s: %s' % (name, tag.__class__.__name__, vendor)))
     return tag_content
 def get(self):
     css = []
     for tag_name, tag_class in tags.get_tag_bindings().items():
         css.append(
             '.yui-toolbar-%(tag_name)s > .yui-toolbar-icon {'
             '  background: url(%(icon_url)s) !important;'
             '  background-size: 100%% !important;'
             '  left: 5px;'
             '}' % {
                 'tag_name': tag_name,
                 'icon_url': tag_class().get_icon_url()})
     # Ensure this resource is cacheable.
     sites.set_static_resource_cache_control(self)
     self.response.headers['Content-Type'] = 'text/css'
     self.response.out.write('\n'.join(css))
Beispiel #5
0
    def get_edit_custom_tag(self):
        """Return the the page used to edit a custom HTML tag in a popup."""
        tag_name = self.request.get('tag_name')
        tag_bindings = tags.get_tag_bindings()
        tag_class = tag_bindings[tag_name]
        schema = tag_class().get_schema(self)
        if schema.has_subregistries():
            raise NotImplementedError()

        template_values = {}
        template_values['form_html'] = ObjectEditor.get_html_for(
            self, schema.get_json_schema(), schema.get_schema_dict(), None,
            None, None)
        self.response.out.write(
            self.get_template('popup.html', []).render(template_values))
Beispiel #6
0
    def get_edit_custom_tag(self):
        """Return the the page used to edit a custom HTML tag in a popup."""
        tag_name = self.request.get('tag_name')
        tag_bindings = tags.get_tag_bindings()
        tag_class = tag_bindings[tag_name]
        schema = tag_class().get_schema(self)
        if schema.has_subregistries():
            raise NotImplementedError()

        template_values = {}
        template_values['form_html'] = ObjectEditor.get_html_for(
            self, schema.get_json_schema(), schema.get_schema_dict(), None,
            None, None)
        self.response.out.write(
            self.get_template('popup.html', []).render(template_values))
    def get_add_custom_tag(self):
        """Return the page for the popup used to add a custom HTML tag."""
        tag_name = self.request.get('tag_name')
        excluded_tags = self.request.get_all('excluded_tags')

        tag_bindings = tags.get_tag_bindings()

        select_data = []
        for name in tag_bindings.keys():
            if name not in excluded_tags:
                clazz = tag_bindings[name]
                select_data.append(
                    (name, '%s: %s' % (clazz.vendor(), clazz.name())))
        select_data = sorted(select_data, key=lambda pair: pair[1])

        if tag_name:
            tag_class = tag_bindings[tag_name]
        else:
            tag_class = tag_bindings[select_data[0][0]]
        tag = tag_class()
        tag_schema = tag.get_schema(self)
        tag_schema = self._validate_schema(tag, tag_schema)

        schema = schema_fields.FieldRegistry('Add a Component')
        type_select = schema.add_sub_registry('type', 'Component Type')
        type_select.add_property(
            schema_fields.SchemaField('tag',
                                      'Name',
                                      'string',
                                      select_data=select_data))
        schema.add_sub_registry('attributes', registry=tag_schema)

        template_values = {}
        template_values['form_html'] = ObjectEditor.get_html_for(
            self,
            schema.get_json_schema(),
            schema.get_schema_dict(),
            None,
            None,
            None,
            required_modules=tag_class.required_modules(),
            extra_js_files=['add_custom_tag.js'] + tag_class.extra_js_files(),
            extra_css_files=tag_class.extra_css_files(),
            additional_dirs=tag_class.additional_dirs())
        self.response.out.write(
            self.get_template('popup.html', []).render(template_values))
Beispiel #8
0
    def get_edit_custom_tag(self):
        """Return the the page used to edit a custom HTML tag in a popup."""
        tag_name = self.request.get('tag_name')
        tag_bindings = tags.get_tag_bindings()
        tag_class = tag_bindings[tag_name]
        tag = tag_class()
        schema = tag.get_schema(self)
        schema = self._validate_schema(tag, schema)

        template_values = {}
        template_values['form_html'] = ObjectEditor.get_html_for(
            self, schema.get_json_schema(), schema.get_schema_dict(), None,
            None, None,
            required_modules=tag_class.required_modules(),
            extra_js_files=tag_class.extra_js_files(),
            extra_css_files=tag_class.extra_css_files(),
            additional_dirs=tag_class.additional_dirs())
        self.response.out.write(
            self.get_template('popup.html', []).render(template_values))
    def get_edit_custom_tag(self):
        """Return the the page used to edit a custom HTML tag in a popup."""
        tag_name = self.request.get('tag_name')
        tag_bindings = tags.get_tag_bindings()
        tag_class = tag_bindings[tag_name]
        tag = tag_class()
        schema = tag.get_schema(self)
        schema = self._validate_schema(tag, schema)

        template_values = {}
        template_values['form_html'] = ObjectEditor.get_html_for(
            self, schema.get_json_schema(), schema.get_schema_dict(), None,
            None, None,
            required_modules=tag_class.required_modules(),
            extra_js_files=tag_class.extra_js_files(),
            extra_css_files=tag_class.extra_css_files(),
            additional_dirs=tag_class.additional_dirs())
        self.response.out.write(
            self.get_template('popup.html', []).render(template_values))
    def get_html_for(
        cls, handler, schema_json, annotations, object_key,
        rest_url, exit_url,
        additional_dirs=None,
        auto_return=False,
        delete_url=None, delete_message=None, delete_method='post',
        delete_button_caption='Delete',
        display_types=None,
        exit_button_caption='Close',
        extra_args=None,
        extra_css_files=None,
        extra_js_files=None,
        extra_required_modules=None,
        read_only=False,
        required_modules=None,
        save_button_caption='Save',
        save_method='put'):
        """Creates an HTML code needed to embed and operate this form.

        This method creates an HTML, JS and CSS  required to embed JSON
        schema-based object editor into a view.

        Args:
            handler: a BaseHandler class, which will host this HTML, JS and CSS
            schema_json: a text of JSON schema for the object being edited
            annotations: schema annotations dictionary
            object_key: a key of an object being edited
            rest_url: a REST endpoint for object GET/PUT operation
            exit_url: a URL to go to after the editor form is dismissed
            auto_return: whether to return to the exit_url on successful save
            additional_dirs: list of extra directories to look for
                Jinja template files, e.g., JS or CSS files included by modules.
            delete_url: optional URL for delete operation
            delete_message: string. Optional custom delete confirmation message
            delete_method: optional HTTP method for delete operation
            delete_button_caption: string. A caption for the 'Delete' button
            display_types: list of strings. All schema field types
            exit_button_caption: a caption for the 'Close' button
            extra_args: extra request params passed back in GET and POST
            extra_css_files: list of extra CSS files to be included
            extra_js_files: list of extra JS files to be included
            extra_required_modules: list of strings.
                inputex modules not covered by display_types
            read_only: optional flag; if set, removes Save and Delete operations
            required_modules: list of inputex modules required for this editor
            save_button_caption: a caption for the 'Save' button
            save_method: how the data should be saved to the server (put|upload)

        Returns:
            The HTML, JS and CSS text that will instantiate an object editor.
        """

        if required_modules:
            if not set(required_modules).issubset(set(ALL_MODULES)):
                difference = set(required_modules).difference(set(ALL_MODULES))
                raise ValueError(
                    "Unsupported inputEx modules were required: {}".format(
                        difference))
        elif display_types:
            required_modules = list(set(
                TYPES_TO_MODULES[type_name] for type_name in display_types))
        else:
            required_modules = ALL_MODULES

        if extra_required_modules:
            required_modules += extra_required_modules

        if not delete_message:
            kind = transforms.loads(schema_json).get('description')
            if not kind:
                kind = 'content'
            delete_message = 'Are you sure you want to delete this %s?' % kind

        # construct parameters
        get_url = rest_url
        get_args = {'key': object_key}
        post_url = rest_url
        post_args = {'key': object_key}

        if extra_args:
            get_args.update(extra_args)
            post_args.update(extra_args)

        if read_only:
            post_url = ''
            post_args = ''

        rte_tag_data = []
        for tag, tag_class in tags.get_tag_bindings().items():
            rte_tag_data.append({
                'name': tag,
                'vendor': tag_class.vendor(),
                'label': tag_class.name(),
                'iconUrl': tag_class().get_icon_url()})

        editor_prefs = {
            'xsrf_token': crypto.XsrfTokenManager.create_xsrf_token(
                EditorPrefsRestHandler.XSRF_TOKEN),
            'location': rest_url,
            'key': object_key,
            'prefs': {}
        }
        user = users.get_current_user()
        if user is not None:
            key_name = EditorPrefsDao.create_key_name(
                user.user_id(), rest_url, object_key)
            editor_prefs_dto = EditorPrefsDao.load(key_name)
            if editor_prefs_dto:
                editor_prefs['prefs'] = editor_prefs_dto.dict

        extra_script_tag_urls = []
        for callback in cls.EXTRA_SCRIPT_TAG_URLS:
            for url in callback():
                extra_script_tag_urls.append(url)

        template_values = {
            'enabled': custom_module.enabled,
            'schema': schema_json,
            'get_url': '%s?%s' % (get_url, urllib.urlencode(get_args, True)),
            'save_url': post_url,
            'save_args': transforms.dumps(post_args),
            'exit_button_caption': exit_button_caption,
            'exit_url': exit_url or '',
            'required_modules': COMMON_REQUIRED_MODULES + required_modules,
            'extra_css_files': extra_css_files or [],
            'extra_js_files': extra_js_files or [],
            'schema_annotations': [
                (item[0], transforms.dumps(item[1])) for item in annotations],
            'save_method': save_method,
            'auto_return': auto_return,
            'delete_button_caption': delete_button_caption,
            'save_button_caption': save_button_caption,
            'rte_tag_data': transforms.dumps(rte_tag_data),
            'delete_message': delete_message,
            'preview_xsrf_token': crypto.XsrfTokenManager.create_xsrf_token(
                PreviewHandler.XSRF_TOKEN),
            'editor_prefs': transforms.dumps(editor_prefs),
            'extra_script_tag_urls': extra_script_tag_urls
        }

        if delete_url and not read_only:
            template_values['delete_url'] = delete_url
        if delete_method:
            template_values['delete_method'] = delete_method
        if appengine_config.BUNDLE_LIB_FILES:
            template_values['bundle_lib_files'] = True

        return jinja2.utils.Markup(handler.get_template('oeditor.html', (
            [TEMPLATES_DIR] + (additional_dirs or [])
        )).render(template_values))
Beispiel #11
0
    def get_deployment(self):
        """Shows server environment and deployment information page."""
        template_values = {}
        template_values['page_title'] = self.format_title('Deployment')
        template_values['page_description'] = messages.DEPLOYMENT_DESCRIPTION

        # modules
        module_content = safe_dom.NodeList()
        module_content.append(
            safe_dom.Element('h3').add_text('Custom Modules'))
        ol = safe_dom.Element('ol')
        module_content.append(ol)
        for name in sorted(custom_modules.Registry.registered_modules.keys()):
            enabled_text = ''
            if name not in custom_modules.Registry.enabled_module_names:
                enabled_text = ' (disabled)'

            li = safe_dom.Element('li').add_text('%s%s' % (name, enabled_text))
            ol.add_child(li)

            amodule = custom_modules.Registry.registered_modules.get(name)
            self._make_routes_dom(
                li, amodule.global_routes, 'Global Routes')
            self._make_routes_dom(
                li, amodule.namespaced_routes, 'Namespaced Routes')

        # Custom tags.
        tag_content = safe_dom.NodeList()
        tag_content.append(
            safe_dom.Element('h3').add_text('Custom Tags'))
        ol = safe_dom.Element('ol')
        tag_content.append(ol)
        tag_bindings = tags.get_tag_bindings()
        for name in sorted(tag_bindings.keys()):
            clazz = tag_bindings.get(name)
            tag = clazz()
            vendor = tag.vendor()
            ol.add_child(safe_dom.Element('li').add_text(
                '%s: %s: %s' % (name, tag.__class__.__name__, vendor)))

        # Yaml file content.
        yaml_content = safe_dom.NodeList()
        yaml_content.append(
            safe_dom.Element('h3').add_text('Contents of ').add_child(
                safe_dom.Element('code').add_text('app.yaml')))
        ol = safe_dom.Element('ol')
        yaml_content.append(ol)
        yaml_lines = open(os.path.join(os.path.dirname(
            __file__), '../../app.yaml'), 'r').readlines()
        for line in yaml_lines:
            ol.add_child(safe_dom.Element('li').add_text(line))

        # Application identity.
        app_id = app.get_application_id()
        app_dict = {}
        app_dict['application_id'] = escape(app_id)
        app_dict['default_ver_hostname'] = escape(
            app.get_default_version_hostname())

        template_values['main_content'] = safe_dom.NodeList().append(
            self.render_dict(app_dict, 'About the Application')
        ).append(
            module_content
        ).append(
            tag_content
        ).append(
            yaml_content
        ).append(
            self.render_dict(os.environ, 'Server Environment Variables'))

        self.render_page(template_values)
Beispiel #12
0
    def get_deployment(self):
        """Shows server environment and deployment information page."""
        template_values = {}
        template_values['page_title'] = self.format_title('Deployment')
        template_values['page_description'] = messages.DEPLOYMENT_DESCRIPTION

        # modules
        module_content = safe_dom.NodeList()
        module_content.append(safe_dom.Element('h3').add_text('Modules'))
        ol = safe_dom.Element('ol')
        module_content.append(ol)
        for name in sorted(custom_modules.Registry.registered_modules.keys()):
            enabled_text = ''
            if name not in custom_modules.Registry.enabled_module_names:
                enabled_text = ' (disabled)'

            li = safe_dom.Element('li').add_text('%s%s' % (name, enabled_text))
            ol.add_child(li)

            amodule = custom_modules.Registry.registered_modules.get(name)
            self._make_routes_dom(li, amodule.global_routes, 'Global Routes')
            self._make_routes_dom(li, amodule.namespaced_routes,
                                  'Namespaced Routes')

        # Custom tags.
        tag_content = safe_dom.NodeList()
        tag_content.append(safe_dom.Element('h3').add_text('Custom Tags'))
        ol = safe_dom.Element('ol')
        tag_content.append(ol)
        tag_bindings = tags.get_tag_bindings()
        for name in sorted(tag_bindings.keys()):
            clazz = tag_bindings.get(name)
            tag = clazz()
            vendor = tag.vendor()
            ol.add_child(
                safe_dom.Element('li').add_text(
                    '%s: %s: %s' % (name, tag.__class__.__name__, vendor)))

        # Yaml file content.
        yaml_content = safe_dom.NodeList()
        yaml_content.append(
            safe_dom.Element('h3').add_text('Contents of ').add_child(
                safe_dom.Element('code').add_text('app.yaml')))
        ol = safe_dom.Element('ol')
        yaml_content.append(ol)
        yaml_lines = open(
            os.path.join(os.path.dirname(__file__), '../../app.yaml'),
            'r').readlines()
        for line in yaml_lines:
            ol.add_child(safe_dom.Element('li').add_text(line))

        # Describe DB entity types
        entity_content = safe_dom.NodeList()
        entity_content.append(
            safe_dom.Element('h3').add_text('Database Entities'))
        entity_content.append(self._describe_db_types())
        entity_content.append(safe_dom.Element('p'))

        # Application identity and users service information.
        app_id = app.get_application_id()
        app_dict = {}
        app_dict['application_id'] = escape(app_id)
        app_dict['default_ver_hostname'] = escape(
            app.get_default_version_hostname())
        app_dict['users_service_name'] = escape(
            users.UsersServiceManager.get().get_service_name())

        # sys.path information.
        sys_path_content = safe_dom.NodeList()
        sys_path_content.append(safe_dom.Element('h3').add_text('sys.path'))
        ol = safe_dom.Element('ol')
        sys_path_content.append(ol)
        for path in sys.path:
            ol.add_child(safe_dom.Element('li').add_text(path))

        template_values['main_content'] = safe_dom.NodeList().append(
            self.render_dict(app_dict, 'About the Application')
        ).append(module_content).append(entity_content).append(
            tag_content).append(yaml_content).append(
                self.render_dict(
                    os.environ,
                    'Server Environment Variables')).append(sys_path_content)
        self.render_page(template_values)
    def get_html_for(cls,
                     handler,
                     schema_json,
                     annotations,
                     object_key,
                     rest_url,
                     exit_url,
                     extra_args=None,
                     save_method='put',
                     delete_url=None,
                     delete_message=None,
                     delete_method='post',
                     auto_return=False,
                     read_only=False,
                     required_modules=None,
                     extra_css_files=None,
                     extra_js_files=None,
                     additional_dirs=None,
                     delete_button_caption='Delete',
                     save_button_caption='Save',
                     exit_button_caption='Close'):
        """Creates an HTML code needed to embed and operate this form.

        This method creates an HTML, JS and CSS  required to embed JSON
        schema-based object editor into a view.

        Args:
            handler: a BaseHandler class, which will host this HTML, JS and CSS
            schema_json: a text of JSON schema for the object being edited
            annotations: schema annotations dictionary
            object_key: a key of an object being edited
            rest_url: a REST endpoint for object GET/PUT operation
            exit_url: a URL to go to after the editor form is dismissed
            extra_args: extra request params passed back in GET and POST
            save_method: how the data should be saved to the server (put|upload)
            delete_url: optional URL for delete operation
            delete_message: string. Optional custom delete confirmation message
            delete_method: optional HTTP method for delete operation
            auto_return: whether to return to the exit_url on successful save
            read_only: optional flag; if set, removes Save and Delete operations
            required_modules: list of inputex modules required for this editor
            extra_css_files: list of extra CSS files to be included
            extra_js_files: list of extra JS files to be included
            additional_dirs: list of extra directories to look for
                Jinja template files, e.g., JS or CSS files included by modules.
            delete_button_caption: string. A caption for the 'Delete' button
            save_button_caption: a caption for the 'Save' button
            exit_button_caption: a caption for the 'Close' button

        Returns:
            The HTML, JS and CSS text that will instantiate an object editor.
        """
        required_modules = required_modules or ALL_MODULES

        if not delete_message:
            kind = transforms.loads(schema_json).get('description')
            if not kind:
                kind = 'Generic Object'
            delete_message = 'Are you sure you want to delete this %s?' % kind

        # construct parameters
        get_url = rest_url
        get_args = {'key': object_key}
        post_url = rest_url
        post_args = {'key': object_key}

        if extra_args:
            get_args.update(extra_args)
            post_args.update(extra_args)

        if read_only:
            post_url = ''
            post_args = ''

        custom_rte_tag_icons = []
        for tag, tag_class in tags.get_tag_bindings().items():
            custom_rte_tag_icons.append({
                'name': tag,
                'iconUrl': tag_class().get_icon_url()
            })

        extra_script_tag_urls = []
        for callback in cls.EXTRA_SCRIPT_TAG_URLS:
            for url in callback():
                extra_script_tag_urls.append(url)

        template_values = {
            'enabled':
            custom_module.enabled,
            'schema':
            schema_json,
            'get_url':
            '%s?%s' % (get_url, urllib.urlencode(get_args, True)),
            'save_url':
            post_url,
            'save_args':
            transforms.dumps(post_args),
            'exit_button_caption':
            exit_button_caption,
            'exit_url':
            exit_url,
            'required_modules':
            COMMON_REQUIRED_MODULES + required_modules,
            'extra_css_files':
            extra_css_files or [],
            'extra_js_files':
            extra_js_files or [],
            'schema_annotations':
            [(item[0], transforms.dumps(item[1])) for item in annotations],
            'save_method':
            save_method,
            'auto_return':
            auto_return,
            'delete_button_caption':
            delete_button_caption,
            'save_button_caption':
            save_button_caption,
            'custom_rte_tag_icons':
            transforms.dumps(custom_rte_tag_icons),
            'delete_message':
            delete_message,
            'can_highlight_code':
            CAN_HIGHLIGHT_CODE.value,
            'extra_script_tag_urls':
            extra_script_tag_urls,
        }

        if delete_url and not read_only:
            template_values['delete_url'] = delete_url
        if delete_method:
            template_values['delete_method'] = delete_method
        if appengine_config.BUNDLE_LIB_FILES:
            template_values['bundle_lib_files'] = True

        return jinja2.utils.Markup(
            handler.get_template(
                'oeditor.html',
                ([os.path.dirname(__file__)] +
                 (additional_dirs or []))).render(template_values))
Beispiel #14
0
    def get_html_for(
        cls, handler, schema_json, annotations, object_key,
        rest_url, exit_url,
        extra_args=None,
        save_method='put',
        delete_url=None, delete_message=None, delete_method='post',
        auto_return=False, read_only=False,
        required_modules=None,
        extra_css_files=None,
        extra_js_files=None,
        delete_button_caption='Delete',
        save_button_caption='Save',
        exit_button_caption='Close'):
        """Creates an HTML code needed to embed and operate this form.

        This method creates an HTML, JS and CSS  required to embed JSON
        schema-based object editor into a view.

        Args:
            handler: a BaseHandler class, which will host this HTML, JS and CSS
            schema_json: a text of JSON schema for the object being edited
            annotations: schema annotations dictionary
            object_key: a key of an object being edited
            rest_url: a REST endpoint for object GET/PUT operation
            exit_url: a URL to go to after the editor form is dismissed
            extra_args: extra request params passed back in GET and POST
            save_method: how the data should be saved to the server (put|upload)
            delete_url: optional URL for delete operation
            delete_message: string. Optional custom delete confirmation message
            delete_method: optional HTTP method for delete operation
            auto_return: whether to return to the exit_url on successful save
            read_only: optional flag; if set, removes Save and Delete operations
            required_modules: list of inputex modules required for this editor
            extra_css_files: list of extra CSS files to be included
            extra_js_files: list of extra JS files to be included
            delete_button_caption: string. A caption for the 'Delete' button
            save_button_caption: a caption for the 'Save' button
            exit_button_caption: a caption for the 'Close' button

        Returns:
            The HTML, JS and CSS text that will instantiate an object editor.
        """
        required_modules = required_modules or ALL_MODULES

        if not delete_message:
            kind = transforms.loads(schema_json).get('description')
            if not kind:
                kind = 'Generic Object'
            delete_message = 'Are you sure you want to delete this %s?' % kind

        # construct parameters
        get_url = rest_url
        get_args = {'key': object_key}
        post_url = rest_url
        post_args = {'key': object_key}

        if extra_args:
            get_args.update(extra_args)
            post_args.update(extra_args)

        if read_only:
            post_url = ''
            post_args = ''

        custom_rte_tag_icons = []
        for tag, tag_class in tags.get_tag_bindings().items():
            custom_rte_tag_icons.append({
                'name': tag,
                'iconUrl': tag_class().get_icon_url()})

        template_values = {
            'enabled': custom_module.enabled,
            'schema': schema_json,
            'get_url': '%s?%s' % (get_url, urllib.urlencode(get_args, True)),
            'save_url': post_url,
            'save_args': transforms.dumps(post_args),
            'exit_button_caption': exit_button_caption,
            'exit_url': exit_url,
            'required_modules': COMMON_REQUIRED_MODULES + required_modules,
            'extra_css_files': extra_css_files or [],
            'extra_js_files': extra_js_files or [],
            'schema_annotations': [
                (item[0], transforms.dumps(item[1])) for item in annotations],
            'save_method': save_method,
            'auto_return': auto_return,
            'delete_button_caption': delete_button_caption,
            'save_button_caption': save_button_caption,
            'custom_rte_tag_icons': transforms.dumps(custom_rte_tag_icons),
            'delete_message': delete_message,
            }

        if delete_url and not read_only:
            template_values['delete_url'] = delete_url
        if delete_method:
            template_values['delete_method'] = delete_method
        if appengine_config.BUNDLE_LIB_FILES:
            template_values['bundle_lib_files'] = True

        return jinja2.utils.Markup(handler.get_template(
            'oeditor.html', [os.path.dirname(__file__)]
        ).render(template_values))