| Index: appengine/monorail/tracker/componentcreate.py
|
| diff --git a/appengine/monorail/tracker/componentcreate.py b/appengine/monorail/tracker/componentcreate.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..1e95b2b3dfbebfba93c1dc84f6259b3e80672054
|
| --- /dev/null
|
| +++ b/appengine/monorail/tracker/componentcreate.py
|
| @@ -0,0 +1,169 @@
|
| +# Copyright 2016 The Chromium Authors. All rights reserved.
|
| +# Use of this source code is govered by a BSD-style
|
| +# license that can be found in the LICENSE file or at
|
| +# https://developers.google.com/open-source/licenses/bsd
|
| +
|
| +"""A servlet for project owners to create a new component def."""
|
| +
|
| +import logging
|
| +import time
|
| +
|
| +from framework import framework_helpers
|
| +from framework import framework_views
|
| +from framework import jsonfeed
|
| +from framework import permissions
|
| +from framework import servlet
|
| +from framework import urls
|
| +from tracker import component_helpers
|
| +from tracker import tracker_bizobj
|
| +from tracker import tracker_constants
|
| +from tracker import tracker_views
|
| +
|
| +from third_party import ezt
|
| +
|
| +
|
| +class ComponentCreate(servlet.Servlet):
|
| + """Servlet allowing project owners to create a component."""
|
| +
|
| + _MAIN_TAB_MODE = servlet.Servlet.MAIN_TAB_PROCESS
|
| + _PAGE_TEMPLATE = 'tracker/component-create-page.ezt'
|
| +
|
| + def AssertBasePermission(self, mr):
|
| + """Check whether the user has any permission to visit this page.
|
| +
|
| + Args:
|
| + mr: commonly used info parsed from the request.
|
| + """
|
| + super(ComponentCreate, self).AssertBasePermission(mr)
|
| + if not self.CheckPerm(mr, permissions.EDIT_PROJECT):
|
| + raise permissions.PermissionException(
|
| + 'User is not allowed to administer this project')
|
| +
|
| + def GatherPageData(self, mr):
|
| + """Build up a dictionary of data values to use when rendering the page.
|
| +
|
| + Args:
|
| + mr: commonly used info parsed from the request.
|
| +
|
| + Returns:
|
| + Dict of values used by EZT for rendering the page.
|
| + """
|
| + config = self.services.config.GetProjectConfig(mr.cnxn, mr.project_id)
|
| + users_by_id = framework_views.MakeAllUserViews(
|
| + mr.cnxn, self.services.user,
|
| + *[list(cd.admin_ids) + list(cd.cc_ids)
|
| + for cd in config.component_defs])
|
| + component_def_views = [
|
| + tracker_views.ComponentDefView(cd, users_by_id)
|
| + # TODO(jrobbins): future component-level view restrictions.
|
| + for cd in config.component_defs]
|
| + for cdv in component_def_views:
|
| + setattr(cdv, 'selected', None)
|
| + path = (cdv.parent_path + '>' + cdv.leaf_name).lstrip('>')
|
| + if path == mr.component_path:
|
| + setattr(cdv, 'selected', True)
|
| +
|
| + return {
|
| + 'parent_path': mr.component_path,
|
| + 'admin_tab_mode': servlet.Servlet.PROCESS_TAB_COMPONENTS,
|
| + 'component_defs': component_def_views,
|
| + 'initial_leaf_name': '',
|
| + 'initial_docstring': '',
|
| + 'initial_deprecated': ezt.boolean(False),
|
| + 'initial_admins': [],
|
| + 'initial_cc': [],
|
| + }
|
| +
|
| + def ProcessFormData(self, mr, post_data):
|
| + """Validate and store the contents of the issues tracker admin page.
|
| +
|
| + Args:
|
| + mr: commonly used info parsed from the request.
|
| + post_data: HTML form data from the request.
|
| +
|
| + Returns:
|
| + String URL to redirect the user to, or None if response was already sent.
|
| + """
|
| + config = self.services.config.GetProjectConfig(mr.cnxn, mr.project_id)
|
| + parent_path = post_data.get('parent_path', '')
|
| + parsed = component_helpers.ParseComponentRequest(
|
| + mr, post_data, self.services.user)
|
| +
|
| + if parent_path:
|
| + parent_def = tracker_bizobj.FindComponentDef(parent_path, config)
|
| + if not parent_def:
|
| + self.abort(500, 'parent component not found')
|
| + allow_parent_edit = permissions.CanEditComponentDef(
|
| + mr.auth.effective_ids, mr.perms, mr.project, parent_def, config)
|
| + if not allow_parent_edit:
|
| + raise permissions.PermissionException(
|
| + 'User is not allowed to add a subcomponent here')
|
| +
|
| + path = '%s>%s' % (parent_path, parsed.leaf_name)
|
| + else:
|
| + path = parsed.leaf_name
|
| +
|
| + leaf_name_error_msg = LeafNameErrorMessage(
|
| + parent_path, parsed.leaf_name, config)
|
| + if leaf_name_error_msg:
|
| + mr.errors.leaf_name = leaf_name_error_msg
|
| +
|
| + if mr.errors.AnyErrors():
|
| + self.PleaseCorrect(
|
| + mr, parent_path=parent_path,
|
| + initial_leaf_name=parsed.leaf_name,
|
| + initial_docstring=parsed.docstring,
|
| + initial_deprecated=ezt.boolean(parsed.deprecated),
|
| + initial_admins=parsed.admin_usernames,
|
| + initial_cc=parsed.cc_usernames,
|
| + )
|
| + return
|
| +
|
| + created = int(time.time())
|
| + creator_id = self.services.user.LookupUserID(
|
| + mr.cnxn, mr.auth.email, autocreate=False)
|
| +
|
| + self.services.config.CreateComponentDef(
|
| + mr.cnxn, mr.project_id, path, parsed.docstring, parsed.deprecated,
|
| + parsed.admin_ids, parsed.cc_ids, created, creator_id)
|
| +
|
| + return framework_helpers.FormatAbsoluteURL(
|
| + mr, urls.ADMIN_COMPONENTS, saved=1, ts=int(time.time()))
|
| +
|
| +
|
| +class CheckComponentNameJSON(jsonfeed.JsonFeed):
|
| + """JSON data for handling name checks when creating a component."""
|
| +
|
| + def HandleRequest(self, mr):
|
| + """Provide the UI with info about the availability of the component name.
|
| +
|
| + Args:
|
| + mr: common information parsed from the HTTP request.
|
| +
|
| + Returns:
|
| + Results dictionary in JSON format.
|
| + """
|
| + parent_path = mr.GetParam('parent_path')
|
| + leaf_name = mr.GetParam('leaf_name')
|
| + config = self.services.config.GetProjectConfig(mr.cnxn, mr.project_id)
|
| + message = LeafNameErrorMessage(parent_path, leaf_name, config)
|
| +
|
| + return {
|
| + 'error_message': message,
|
| + }
|
| +
|
| +
|
| +def LeafNameErrorMessage(parent_path, leaf_name, config):
|
| + """Return an error message for the given component name, or None."""
|
| + if not tracker_constants.COMPONENT_NAME_RE.match(leaf_name):
|
| + return 'Invalid component name'
|
| +
|
| + if parent_path:
|
| + path = '%s>%s' % (parent_path, leaf_name)
|
| + else:
|
| + path = leaf_name
|
| +
|
| + if tracker_bizobj.FindComponentDef(path, config):
|
| + return 'That name is already in use.'
|
| +
|
| + return None
|
|
|