Skip to content

Commit

Permalink
Merge a72d9c5 into 350bffe
Browse files Browse the repository at this point in the history
  • Loading branch information
kakshay21 committed Jul 16, 2018
2 parents 350bffe + a72d9c5 commit fb40e92
Show file tree
Hide file tree
Showing 13 changed files with 1,125 additions and 1 deletion.
5 changes: 4 additions & 1 deletion CHANGES.rst
Expand Up @@ -4,6 +4,9 @@ Changelog
3.3.1 (unreleased)
------------------

- Add view subtemplate
[kakshay21]

- Add update_locale script in bin/ folder to update locales
[kakshay21]

Expand Down Expand Up @@ -168,7 +171,7 @@ Changelog

- Fixed the pypi index to explicitly reference https://pypi.python.org/simple/ to prevent buildout from defaulting to the old and unsupported http:// url.
[pigeonflight]

- Fix coveralls for packages created with addon and theme_package by converting the pickle output of createcoverage in .coverage to json.
[pbauer]

Expand Down
8 changes: 8 additions & 0 deletions bobtemplates/plone/bobregistry.py
Expand Up @@ -45,6 +45,14 @@ def plone_content_type():
return reg


def plone_view():
reg = RegEntry()
reg.template = 'bobtemplates.plone:view'
reg.plonecli_alias = 'view'
reg.depend_on = 'plone_addon'
return reg


def plone_vocabulary():
reg = RegEntry()
reg.template = 'bobtemplates.plone:vocabulary'
Expand Down
203 changes: 203 additions & 0 deletions bobtemplates/plone/view.py
@@ -0,0 +1,203 @@
# -*- coding: utf-8 -*-
"""Generate view."""

from bobtemplates.plone.base import base_prepare_renderer
from bobtemplates.plone.base import git_commit
from bobtemplates.plone.base import update_file
from lxml import etree
from mrbob.bobexceptions import SkipQuestion
from mrbob.bobexceptions import ValidationError

import os
import stringcase


def get_view_name_from_python_class(configurator, question):
"""Generate view default name from python class"""
if configurator.variables['view_python_class']:
view_class_name = configurator.variables['view_python_class_name']
view_generated_name = stringcase.snakecase(view_class_name).replace('_', '-') # NOQA: E501
question.default = view_generated_name
else:
question.default = 'my-view'


def get_template_name_default(configurator, question):
if configurator.variables['view_template']:
view_url = configurator.variables['view_name']
template_default_name = stringcase.snakecase(view_url)
question.default = template_default_name
else:
question.default = 'view'


def check_python_class_answer(configurator, question):
if not configurator.variables['view_python_class']:
raise SkipQuestion(u'No python class, so we skip python class name question.') # NOQA: E501


def check_view_template_answer(configurator, question):
if not configurator.variables['view_template'] and not configurator.variables['view_python_class']: # NOQA: E501
raise ValidationError(u'View must at least have a template or a python class') # NOQA: E501
elif not configurator.variables['view_template']:
raise SkipQuestion(u'No view template, so we skip view template name question.') # NOQA: E501


def _update_views_configure_zcml(configurator):
file_name = u'configure.zcml'
directory_path = configurator.variables['package_folder'] + '/views/'
file_path = directory_path + file_name
configure_example_file_path = configurator.variables['package_folder'] + '/views/configure.zcml.example' # NOQA: E501
file_list = os.listdir(os.path.dirname(directory_path))
if file_name not in file_list:
os.rename(configure_example_file_path, file_path)
namespaces = '{http://namespaces.zope.org/zope}'

with open(file_path, 'r') as xml_file:
parser = etree.XMLParser(remove_blank_text=True)
tree = etree.parse(xml_file, parser)
tree_root = tree.getroot()
view_xpath = "{0}browser:page[@name='{1}']".format(
namespaces,
configurator.variables['view_name'],
)
if len(tree_root.findall(view_xpath)):
print(
'{0} already in configure.zcml, skip adding!'.format(
configurator.variables['view_name'],
),
)
return

match_str = '-*- extra stuff goes here -*-'

if configurator.variables['view_template'] and configurator.variables['view_python_class']: # NOQA: E501
insert_str = """
<browser:page
name="{0}"
for="Products.CMFCore.interfaces.IFolderish"
class=".{1}.{2}"
template="{3}.pt"
permission="zope2.View"
/>
""".format(
configurator.variables['view_name'],
configurator.variables['view_python_file_name'],
configurator.variables['view_python_class_name'],
configurator.variables['view_template_name'],
)

if configurator.variables['view_template'] and not configurator.variables['view_python_class']: # NOQA: E501
insert_str = """
<browser:page
name="{0}"
for="Products.CMFCore.interfaces.IFolderish"
template="{1}.pt"
permission="zope2.View"
/>
""".format(
configurator.variables['view_name'],
configurator.variables['view_template_name'],
)

if not configurator.variables['view_template'] and configurator.variables['view_python_class']: # NOQA: E501
insert_str = """
<browser:page
name="{0}"
for="Products.CMFCore.interfaces.IFolderish"
class=".{1}.{2}"
permission="zope2.View"
/>
""".format(
configurator.variables['view_name'],
configurator.variables['view_python_file_name'],
configurator.variables['view_python_class_name'],
)

update_file(configurator, file_path, match_str, insert_str)


def _update_configure_zcml(configurator):
file_name = u'configure.zcml'
file_path = configurator.variables['package_folder'] + '/' + file_name
namespaces = '{http://namespaces.zope.org/zope}'

with open(file_path, 'r') as xml_file:
parser = etree.XMLParser(remove_blank_text=True)
tree = etree.parse(xml_file, parser)
tree_root = tree.getroot()
view_xpath = "{0}include[@package='.views']".format(namespaces)
if len(tree_root.findall(view_xpath)):
print(
'.views already in configure.zcml, skip adding!',
)
return

match_str = '<!--<includeDependencies package="." />-->'
insert_str = """
<include package=".views" />
"""
update_file(configurator, file_path, match_str, insert_str)


def _delete_unwanted_files(configurator):
directory_path = configurator.variables['package_folder'] + '/views/'
if not configurator.variables['view_template']:
file_name = u'{0}.pt'.format(
configurator.variables['view_template_name'],
)
file_path = directory_path + file_name
os.remove(file_path)

elif not configurator.variables['view_python_class']:
file_name = u'{0}.py'.format(
configurator.variables['view_python_file_name'],
)
file_path = directory_path + file_name
os.remove(file_path)

file_name = u'configure.zcml.example'
file_list = os.listdir(os.path.dirname(directory_path))
if file_name in file_list:
file_path = directory_path + file_name
os.remove(file_path)


def prepare_renderer(configurator):
"""Prepare rendering."""
configurator = base_prepare_renderer(configurator)
configurator.variables['template_id'] = 'view'
view_name = configurator.variables['view_name'].strip('_')
normalized_view_name = stringcase.snakecase(view_name)
configurator.variables['view_name_normalized'] = normalized_view_name
if configurator.variables['view_python_class']:
python_class_name = configurator.variables['view_python_class_name'].strip('_') # NOQA: E501
configurator.variables['view_python_class_name'] = stringcase.pascalcase( # NOQA: E501
python_class_name,
)
view_python_file_name = stringcase.snakecase(python_class_name)
configurator.variables['view_python_file_name'] = view_python_file_name
view_name_from_input = normalized_view_name.replace('_', '-')
view_name_from_python_class = view_python_file_name.replace('_', '-')
if view_name_from_input != view_name_from_python_class:
configurator.variables['view_name'] = view_name_from_input
else:
configurator.variables['view_python_file_name'] = view_name

if not configurator.variables['view_template']:
configurator.variables['view_template_name'] = view_name

configurator.target_directory = configurator.variables['package_folder']


def post_renderer(configurator):
"""Post rendering."""
_update_configure_zcml(configurator)
_update_views_configure_zcml(configurator)
_delete_unwanted_files(configurator)
git_commit(
configurator,
'Add view: {0}'.format(
configurator.variables['view_name'],
),
)
49 changes: 49 additions & 0 deletions bobtemplates/plone/view/.mrbob.ini
@@ -0,0 +1,49 @@
[questions]
subtemplate_warning.question = Please commit your changes, before using a sub-template! Continue anyway? [n/y]
subtemplate_warning.required = True
subtemplate_warning.default = n
subtemplate_warning.pre_ask_question = bobtemplates.plone.base:git_clean_state_check
subtemplate_warning.post_ask_question = mrbob.hooks:validate_choices bobtemplates.plone.base:subtemplate_warning_post_question
subtemplate_warning.choices = y|n
subtemplate_warning.choices_delimiter = |

view_python_class.question = Should the view have a Python class?
view_python_class.help = Do you want Python class for this view?
view_python_class.required = True
view_python_class.default = y
view_python_class.pre_ask_question = bobtemplates.plone.base:check_root_folder
view_python_class.post_ask_question = mrbob.hooks:validate_choices mrbob.hooks:to_boolean
view_python_class.choices = y|n
view_python_class.choices_delimiter = |

view_python_class_name.question = Python class name
view_python_class_name.help = Shold be something like 'MyView'
view_python_class_name.required = False
view_python_class_name.default = MyView
view_python_class_name.pre_ask_question = bobtemplates.plone.view:check_python_class_answer

view_name.question = View name (part of the URL)
view_name.help = Should be something like 'my-view' (no special characters!)
view_name.required = True
view_name.default = my-view
view_name.pre_ask_question = bobtemplates.plone.view:get_view_name_from_python_class


view_template.question = Should the View have a template file?
view_template.help = Do you want a template file for this view?
view_template.required = True
view_template.default = y
view_template.post_ask_question = mrbob.hooks:validate_choices mrbob.hooks:to_boolean
view_template.choices = y|n
view_template.choices_delimiter = |

view_template_name.question = Template name (without extension)
view_template_name.help = name of the template file for this view
view_template_name.default = my_view
view_template_name.required = False
view_template_name.pre_ask_question = bobtemplates.plone.view:check_view_template_answer bobtemplates.plone.view:get_template_name_default

[template]
post_ask = bobtemplates.plone.base:set_global_vars
pre_render = bobtemplates.plone.view:prepare_renderer
post_render = bobtemplates.plone.view:post_renderer
@@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
from {{{package.dottedname}}}.testing import {{{package.uppercasename}}}_FUNCTIONAL_TESTING
from {{{package.dottedname}}}.testing import {{{package.uppercasename}}}_INTEGRATION_TESTING
from plone import api
from plone.app.testing import setRoles
from plone.app.testing import TEST_USER_ID
from zope.component import getMultiAdapter
from zope.component.interfaces import ComponentLookupError

import unittest


class ViewsIntegrationTest(unittest.TestCase):

layer = {{{package.uppercasename}}}_INTEGRATION_TESTING

def setUp(self):
self.portal = self.layer['portal']
setRoles(self.portal, TEST_USER_ID, ['Manager'])
api.content.create(self.portal, 'Folder', 'other-folder')
api.content.create(self.portal, 'Collection', 'my-collection')

def test_{{{view_name_normalized}}}_is_registered(self):
view = getMultiAdapter(
(self.portal['other-folder'], self.portal.REQUEST),
name='{{{ view_name }}}'
)
self.assertTrue(view(), '{{{ view_name }}} is not found')
{{% if view_python_class and view_template %}}
self.assertTrue(
'Sample View' in view(),
'Sample View is not found in {{{ view_name }}}'
)
self.assertTrue(
'Sample View' in view(),
'A small message is not found in {{{ view_name }}}'
)
{{% else %}}
self.assertTrue(
'Sample View' in view(),
'Sample View is not found in {{{ view_name }}}'
)
{{% endif %}}

def test_{{{view_name_normalized}}}_in_my_collection(self):
with self.assertRaises(ComponentLookupError):
getMultiAdapter(
(self.portal['my-collection'], self.portal.REQUEST),
name='{{{ view_name }}}'
)


class ViewsFunctionalTest(unittest.TestCase):

layer = {{{package.uppercasename}}}_FUNCTIONAL_TESTING

def setUp(self):
self.portal = self.layer['portal']
setRoles(self.portal, TEST_USER_ID, ['Manager'])
29 changes: 29 additions & 0 deletions bobtemplates/plone/view/views/+view_python_file_name+.py.bob
@@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-

{{% if view_template %}}
from {{{ package.dottedname }}} import _
{{% endif %}}
from Products.Five.browser import BrowserView
{{% if view_template %}}
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
{{% endif %}}


class {{{ view_python_class_name }}}(BrowserView):
{{% if view_template %}}
template = ViewPageTemplateFile('{{{ view_template_name }}}.pt')

def __init__(self, context, request):
self.context = context
self.request = request

def __call__(self):
self.msg = _(u'A small message')
return self.template()
{{% else %}}
def __call__(self):
template = '''<li class="heading" i18n:translate="">
Sample View
</li>'''
return template
{{% endif %}}
20 changes: 20 additions & 0 deletions bobtemplates/plone/view/views/+view_template_name+.pt.bob
@@ -0,0 +1,20 @@
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:metal="http://xml.zope.org/namespaces/metal"
xmlns:tal="http://xml.zope.org/namespaces/tal"
xmlns:i18n="http://xml.zope.org/namespaces/i18n"
i18n:domain="{{{ package.dottedname }}}"
metal:use-macro="context/main_template/macros/master">
<body>
<metal:block fill-slot="content-core">
<div class="heading">
<li i18n:translate="">
Sample View
</li>
{{% if view_python_class %}}
<span tal:content="view/msg">this gets replaced</span>
{{% endif %}}
</div>
</metal:block>

<body>
</html>
Empty file.

0 comments on commit fb40e92

Please sign in to comment.