blob: ce543532e212495558cb646e1720b87e98aa7a15 [file] [log] [blame]
# 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
"""A servlet for project owners to create a new field def."""
import logging
import re
import time
from third_party import ezt
from framework import framework_helpers
from framework import jsonfeed
from framework import permissions
from framework import servlet
from framework import urls
from tracker import field_helpers
from tracker import tracker_constants
from tracker import tracker_helpers
class FieldCreate(servlet.Servlet):
"""Servlet allowing project owners to create a custom field."""
_PAGE_TEMPLATE = 'tracker/field-create-page.ezt'
def AssertBasePermission(self, mr):
"""Check whether the user has any permission to visit this page.
mr: commonly used info parsed from the request.
super(FieldCreate, self).AssertBasePermission(mr)
if not self.CheckPerm(mr, permissions.EDIT_PROJECT):
raise permissions.PermissionException(
'You are not allowed to administer this project')
def GatherPageData(self, mr):
"""Build up a dictionary of data values to use when rendering the page.
mr: commonly used info parsed from the request.
Dict of values used by EZT for rendering the page.
config =, mr.project_id)
well_known_issue_types = tracker_helpers.FilterIssueTypes(config)
return {
'admin_tab_mode': servlet.Servlet.PROCESS_TAB_LABELS,
'initial_field_name': '',
'initial_field_docstring': '',
'initial_is_required': ezt.boolean(False),
'initial_is_multivalued': ezt.boolean(False),
'initial_choices': '',
'initial_admins': '',
'initial_type': 'enum_type',
'initial_applicable_type': '', # That means any issue type
'initial_applicable_predicate': '',
'initial_needs_member': ezt.boolean(False),
'initial_needs_perm': '',
'initial_grants_perm': '',
'initial_notify_on': 0,
'well_known_issue_types': well_known_issue_types,
def ProcessFormData(self, mr, post_data):
"""Validate and store the contents of the issues tracker admin page.
mr: commonly used info parsed from the request.
post_data: HTML form data from the request.
String URL to redirect the user to, or None if response was already sent.
config =, mr.project_id)
parsed = field_helpers.ParseFieldDefRequest(post_data, config)
if not tracker_constants.FIELD_NAME_RE.match(parsed.field_name):
mr.errors.field_name = 'Invalid field name'
field_name_error_msg = FieldNameErrorMessage(parsed.field_name, config)
if field_name_error_msg:
mr.errors.field_name = field_name_error_msg
if (parsed.min_value is not None and parsed.max_value is not None and
parsed.min_value > parsed.max_value):
mr.errors.min_value = 'Minimum value must be less than maximum.'
if parsed.regex:
except re.error:
mr.errors.regex = 'Invalid regular expression.'
admin_ids, admin_str = tracker_helpers.ParseAdminUsers(
mr.cnxn, post_data['admin_names'],
if mr.errors.AnyErrors():
mr, initial_field_name=parsed.field_name,
mr.cnxn, mr.project_id, parsed.field_name, parsed.field_type_str,
parsed.applicable_type, parsed.applicable_predicate,
parsed.is_required, parsed.is_multivalued,
parsed.min_value, parsed.max_value, parsed.regex, parsed.needs_member,
parsed.needs_perm, parsed.grants_perm, parsed.notify_on,
parsed.field_docstring, admin_ids)
if parsed.field_type_str == 'enum_type':
mr.cnxn, mr.project, well_known_labels=parsed.revised_labels)
return framework_helpers.FormatAbsoluteURL(
mr, urls.ADMIN_LABELS, saved=1, ts=int(time.time()))
class CheckFieldNameJSON(jsonfeed.JsonFeed):
"""JSON data for handling name checks when creating a field."""
def HandleRequest(self, mr):
"""Provide the UI with info about the availability of the field name.
mr: common information parsed from the HTTP request.
Results dictionary in JSON format.
field_name = mr.GetParam('field')
config =, mr.project_id)
choices = ExistingEnumChoices(field_name, config)
choices_dicts = [dict(name=choice.name_padded, doc=choice.docstring)
for choice in choices]
message = FieldNameErrorMessage(field_name, config)
return {
'error_message': message,
'choices': choices_dicts,
def FieldNameErrorMessage(field_name, config):
"""Return an error message for the given field name, or None."""
field_name_lower = field_name.lower()
if field_name_lower in tracker_constants.RESERVED_PREFIXES:
return 'That name is reserved.'
for fd in config.field_defs:
fn_lower = fd.field_name.lower()
if field_name_lower == fn_lower:
return 'That name is already in use.'
if field_name_lower.startswith(fn_lower + '-'):
return 'An existing field name is a prefix of that name.'
if fn_lower.startswith(field_name_lower + '-'):
return 'That name is a prefix of an existing field name.'
return None
def ExistingEnumChoices(field_name, config):
"""Return a list of existing label choices for the given prefix."""
# If there are existing labels with that prefix, then it must be enum.
# The existing labels will be treated as field values.
choices = tracker_helpers.LabelsMaskedByFields(
config, [field_name], trim_prefix=True)
return choices