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, field_def = self._GetFieldDef(mr) allow_edit = permissions.CanEditFieldDef( mr.auth.effective_ids, mr.perms, mr.project, field_def) if not allow_edit: raise permissions.PermissionException( 'User is not allowed to delete this field') if 'deletefield' in post_data: self._ProcessDeleteField(mr, field_def) return framework_helpers.FormatAbsoluteURL( mr, urls.ADMIN_LABELS, deleted=1, ts=int(time.time())) else: self._ProcessEditField(mr, post_data, config, field_def) return framework_helpers.FormatAbsoluteURL( mr, urls.FIELD_DETAIL, field=field_def.field_name, saved=1, ts=int(time.time()))
def ProcessFormData(self, mr, post_data): """Process the posted form.""" # 1. Parse and validate user input. user_id, role, extra_perms, notes, ac_exclusion, no_expand = ( self.ParsePersonData(mr, post_data)) member_id = self.ValidateMemberID(mr.cnxn, user_id, mr.project) # 2. Call services layer to save changes. if 'remove' in post_data: self.ProcessRemove(mr, member_id) else: self.ProcessSave(mr, role, extra_perms, notes, member_id, ac_exclusion, no_expand) # 3. Determine the next page in the UI flow. if 'remove' in post_data: return framework_helpers.FormatAbsoluteURL(mr, urls.PEOPLE_LIST, saved=1, ts=int(time.time())) else: return framework_helpers.FormatAbsoluteURL(mr, urls.PEOPLE_DETAIL, u=user_id, saved=1, ts=int(time.time()))
def ProcessFormData(self, mr, post_data): """Process the posted form. Args: mr: commonly used info parsed from the request. post_data: dictionary of HTML form data. Returns: String URL to redirect to after processing is completed. """ if 'savechanges' in post_data: self._ProcessQuota(mr, post_data) else: self._ProcessPublishingOptions(mr, post_data) if 'deletebtn' in post_data: url = framework_helpers.FormatAbsoluteURL(mr, urls.HOSTING_HOME, include_project=False) else: url = framework_helpers.FormatAbsoluteURL(mr, urls.ADMIN_ADVANCED, saved=1, ts=int(time.time())) return url
def testFormatAbsoluteURL_CommonRequestParams(self): _request, mr = testing_helpers.GetRequestObjects( path='/p/proj/some-path?foo=bar&can=1', headers={'Host': 'www.test.com'}) self.assertEqual( 'http://www.test.com/p/proj/some/path?can=1', framework_helpers.FormatAbsoluteURL(mr, '/some/path')) self.assertEqual( 'http://www.test.com/p/proj/some/path', framework_helpers.FormatAbsoluteURL( mr, '/some/path', copy_params=False))
def ProcessRemoveMembers(self, mr, post_data): """Process the user's request to remove members. Args: mr: common information parsed from the HTTP request. post_data: dictionary of form data. Returns: String URL to redirect the user to after processing. """ # 1. Gather data from the request. remove_strs = post_data.getall('remove') logging.info('remove_strs = %r', remove_strs) if not remove_strs: mr.errors.remove = 'No users specified' # 2. Call services layer to save changes. if not mr.errors.AnyErrors(): remove_ids = set( self.services.user.LookupUserIDs(mr.cnxn, remove_strs).values()) self.services.usergroup.RemoveMembers( mr.cnxn, mr.viewed_user_auth.user_id, remove_ids) # 3. Determine the next page in the UI flow. if mr.errors.AnyErrors(): self.PleaseCorrect(mr) else: return framework_helpers.FormatAbsoluteURL( mr, '/g/%s/' % mr.viewed_username, include_project=False, saved=1, ts=int(time.time()))
def ProcessAddMembers(self, mr, post_data): """Process the user's request to add members. Args: mr: common information parsed from the HTTP request. post_data: dictionary of form data. Returns: String URL to redirect the user to after processing. """ # 1. Gather data from the request. group_id = mr.viewed_user_auth.user_id add_members_str = post_data.get('addmembers') new_member_ids = project_helpers.ParseUsernames( mr.cnxn, self.services.user, add_members_str) role = post_data['role'] # 2. Call services layer to save changes. if not mr.errors.AnyErrors(): try: self.services.usergroup.UpdateMembers( mr.cnxn, group_id, new_member_ids, role) except usergroup_svc.CircularGroupException: mr.errors.addmembers = ( 'The members are already ancestors of current group.') # 3. Determine the next page in the UI flow. if mr.errors.AnyErrors(): self.PleaseCorrect( mr, initial_add_members=add_members_str, initially_expand_form=ezt.boolean(True)) else: return framework_helpers.FormatAbsoluteURL( mr, '/g/%s/' % mr.viewed_username, include_project=False, saved=1, ts=int(time.time()))
def ProcessFormData(self, mr, post_data): """Process a posted advanced query form. 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 after processing. """ # Default to searching open issues in this project. can = post_data.get('can', 2) terms = [] self._AccumulateANDTerm('', 'words', post_data, terms) self._AccumulateANDTerm('-', 'without', post_data, terms) self._AccumulateANDTerm('label:', 'labels', post_data, terms) self._AccumulateORTerm('component:', 'components', post_data, terms) self._AccumulateORTerm('status:', 'statuses', post_data, terms) self._AccumulateORTerm('reporter:', 'reporters', post_data, terms) self._AccumulateORTerm('owner:', 'owners', post_data, terms) self._AccumulateORTerm('cc:', 'cc', post_data, terms) self._AccumulateORTerm('commentby:', 'commentby', post_data, terms) if 'starcount' in post_data: starcount = int(post_data['starcount']) if starcount >= 0: terms.append('starcount:%s' % starcount) return framework_helpers.FormatAbsoluteURL(mr, urls.ISSUE_LIST, q=' '.join(terms), can=can)
def testFormatAbsoluteURL(self): _request, mr = testing_helpers.GetRequestObjects( path='/p/proj/some-path', headers={'Host': 'www.test.com'}) self.assertEqual( 'http://www.test.com/p/proj/some/path', framework_helpers.FormatAbsoluteURL(mr, '/some/path'))
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. """ existing_queries = savedqueries_helpers.ParseSavedQueries( mr.cnxn, post_data, self.services.project) added_queries = savedqueries_helpers.ParseSavedQueries( mr.cnxn, post_data, self.services.project, prefix='new_') saved_queries = existing_queries + added_queries self.services.features.UpdateUserSavedQueries( mr.cnxn, mr.viewed_user_auth.user_id, saved_queries) return framework_helpers.FormatAbsoluteURL( mr, '/u/%s%s' % (mr.viewed_username, urls.SAVED_QUERIES), include_project=False, saved=1, ts=int(time.time()))
def ComputeIssueEntryURL(mr, config): """Compute the URL to use for the "New issue" subtab. Args: mr: commonly used info parsed from the request. config: ProjectIssueConfig for the current project. Returns: A URL string to use. It will be simply "entry" in the non-customized case. Otherewise it will be a fully qualified URL that includes some query string parameters. """ if not config.custom_issue_entry_url: return '/p/%s/issues/entry' % (mr.project_name) base_url = config.custom_issue_entry_url sep = '&' if '?' in base_url else '?' token = xsrf.GenerateToken( mr.auth.user_id, '/p/%s%s%s' % (mr.project_name, urls.ISSUE_ENTRY, '.do')) role_name = framework_helpers.GetRoleName(mr.auth.effective_ids, mr.project) continue_url = urllib.quote(framework_helpers.FormatAbsoluteURL( mr, urls.ISSUE_ENTRY + '.do')) return '%s%stoken=%s&role=%s&continue=%s' % ( base_url, sep, urllib.quote(token), urllib.quote(role_name or ''), continue_url)
def _CheckForMovedProject(self, mr, request): """If the project moved, redirect there or to an informational page.""" if not mr.project: return # We are on a site-wide or user page. if not mr.project.moved_to: return # This project has not moved. admin_url = '/p/%s%s' % (mr.project_name, urls.ADMIN_META) if request.path.startswith(admin_url): return # It moved, but we are near the page that can un-move it. logging.info('project %s has moved: %s', mr.project.project_name, mr.project.moved_to) moved_to = mr.project.moved_to if framework_bizobj.RE_PROJECT_NAME.match(moved_to): # Use the redir query parameter to avoid redirect loops. if mr.redir is None: url = framework_helpers.FormatMovedProjectURL(mr, moved_to) if '?' in url: url += '&redir=1' else: url += '?redir=1' logging.info('trusted move to a new project on our site') self.redirect(url, abort=True) logging.info('not a trusted move, will display link to user to click') # Attach the project name as a url param instead of generating a /p/ # link to the destination project. url = framework_helpers.FormatAbsoluteURL(mr, urls.PROJECT_MOVED, include_project=False, copy_params=False, project=mr.project_name) self.redirect(url, abort=True)
def GatherPageData(self, mr): """Build up a dictionary of data values to use when rendering the page.""" # We are not actually in /p/PROJECTNAME, so mr.project_name is None. # Putting the ProjectMoved page inside a moved project would make # the redirect logic much more complicated. if not mr.specified_project: raise monorailrequest.InputException('No project specified') project = self.services.project.GetProjectByName( mr.cnxn, mr.specified_project) if not project: self.abort(404, 'project not found') if not project.moved_to: # Only show this page for projects that are actually moved. # Don't allow hackers to construct misleading links to this servlet. logging.info('attempt to view ProjectMoved for non-moved project: %s', mr.specified_project) self.abort(400, 'This project has not been moved') if framework_bizobj.RE_PROJECT_NAME.match(project.moved_to): moved_to_url = framework_helpers.FormatAbsoluteURL( mr, urls.SUMMARY, include_project=True, project_name=project.moved_to) elif (project.moved_to.startswith('https://') or project.moved_to.startswith('http://')): moved_to_url = project.moved_to else: # Prevent users from using javascript: or any other tricky URL scheme. moved_to_url = '#invalid-destination-url' return { 'project_name': mr.specified_project, 'moved_to_url': moved_to_url, }
def ProcessChangeOwnership(self, mr, post_data): new_owner_id_set = project_helpers.ParseUsernames( mr.cnxn, self.services.user, post_data.get('changeowners')) remain_as_editor = post_data.get('becomeeditor') == 'on' if len(new_owner_id_set) != 1: mr.errors.transfer_ownership = ( 'Please add one valid user email.') else: new_owner_id = new_owner_id_set.pop() if self.services.features.LookupHotlistIDs( mr.cnxn, [mr.hotlist.name], [new_owner_id]): mr.errors.transfer_ownership = ( 'This user already owns a hotlist with the same name') if mr.errors.AnyErrors(): self.PleaseCorrect( mr, initial_new_owner_username=post_data.get('changeowners'), open_dialog=ezt.boolean(True)) else: old_and_new_owner_ids = [new_owner_id] + mr.hotlist.owner_ids (_, editor_ids, follower_ids) = hotlist_helpers.MembersWithoutGivenIDs( mr.hotlist, old_and_new_owner_ids) if remain_as_editor and mr.hotlist.owner_ids: editor_ids.append(mr.hotlist.owner_ids[0]) self.services.features.UpdateHotlistRoles( mr.cnxn, mr.hotlist_id, [new_owner_id], editor_ids, follower_ids) hotlist = self.services.features.GetHotlist(mr.cnxn, mr.hotlist_id) hotlist_url = hotlist_helpers.GetURLOfHotlist( mr.cnxn, hotlist, self.services.user) return framework_helpers.FormatAbsoluteURL( mr,'%s%s' % (hotlist_url, urls.HOTLIST_PEOPLE), saved=1, ts=int(time.time()), include_project=False)
def ProcessRemoveMembers(self, mr, post_data): """Process the user's request to remove members. Args: mr: common information parsed from the HTTP request. post_data: dictionary of form data. Returns: String URL to redirect the user to after processing. """ # 1. Parse and validate user input. remove_strs = post_data.getall('remove') logging.info('remove_strs = %r', remove_strs) remove_ids = set( self.services.user.LookupUserIDs(mr.cnxn, remove_strs).values()) (owner_ids, committer_ids, contributor_ids) = project_helpers.MembersWithoutGivenIDs( mr.project, remove_ids) # 2. Call services layer to save changes. self.services.project.UpdateProjectRoles(mr.cnxn, mr.project.project_id, owner_ids, committer_ids, contributor_ids) # 3. Determine the next page in the UI flow. return framework_helpers.FormatAbsoluteURL(mr, urls.PEOPLE_LIST, saved=1, ts=int(time.time()))
def GatherPageData(self, mr): logging.info( 'Redirecting from approval page to the new issue detail page.') url = framework_helpers.FormatAbsoluteURL(mr, urls.ISSUE_DETAIL, id=mr.local_id) return self.redirect(url, abort=True)
def _ProcessDeleteComponent(self, mr, component_def): """The user wants to delete the specified custom field definition.""" self.services.issue.DeleteComponentReferences( mr.cnxn, component_def.component_id) self.services.config.DeleteComponentDef( mr.cnxn, mr.project_id, component_def.component_id) return framework_helpers.FormatAbsoluteURL( mr, urls.ADMIN_COMPONENTS, deleted=1, ts=int(time.time()))
def testFormatAbsoluteURL_NoProject(self): path = '/some/path' _request, mr = testing_helpers.GetRequestObjects( headers={'Host': 'www.test.com'}, path=path) url = framework_helpers.FormatAbsoluteURL(mr, path, include_project=False) self.assertEqual(url, 'http://www.test.com/some/path')
def _LoginOrIssueEntryURL(mr, config): """Make a URL to sign in, if needed, on the way to entering an issue.""" issue_entry_url = servlet_helpers.ComputeIssueEntryURL(mr, config) if mr.auth.user_id: return issue_entry_url else: after_login_url = framework_helpers.FormatAbsoluteURL( mr, urls.ISSUE_ENTRY_AFTER_LOGIN) return _SafeCreateLoginURL(mr, after_login_url)
def ProcessFormData(self, mr, post_data): """Processes a POST command to delete components. 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) component_defs = self._GetComponentDefs(mr, post_data, config) # Reverse the component_defs so that we start deleting from subcomponents. component_defs.reverse() # Collect errors. perm_errors = [] subcomponents_errors = [] templates_errors = [] # Collect successes. deleted_components = [] for component_def in component_defs: allow_edit = permissions.CanEditComponentDef( mr.auth.effective_ids, mr.perms, mr.project, component_def, config) if not allow_edit: perm_errors.append(component_def.path) subcomponents = tracker_bizobj.FindDescendantComponents( config, component_def) if subcomponents: subcomponents_errors.append(component_def.path) templates = self.services.template.TemplatesWithComponent( mr.cnxn, component_def.component_id) if templates: templates_errors.append(component_def.path) allow_delete = allow_edit and not subcomponents and not templates if allow_delete: self._ProcessDeleteComponent(mr, component_def) deleted_components.append(component_def.path) # Refresh project config after the component deletion. config = self.services.config.GetProjectConfig( mr.cnxn, mr.project_id) return framework_helpers.FormatAbsoluteURL( mr, urls.ADMIN_COMPONENTS, ts=int(time.time()), failed_perm=','.join(perm_errors), failed_subcomp=','.join(subcomponents_errors), failed_templ=','.join(templates_errors), deleted=','.join(deleted_components))
def get(self, **kwargs): """Construct a 302 pointing at project.source_url, or at adminIntro.""" if not self.mr.project: self.response.status = httplib.NOT_FOUND return source_url = self.mr.project.source_url if not source_url: source_url = framework_helpers.FormatAbsoluteURL( self.mr, urls.ADMIN_INTRO, include_project=True) self.response.location = source_url self.response.status = httplib.MOVED_PERMANENTLY
def ProcessFormData(self, mr, post_data): """Process the posted form.""" if post_data.get('deletestate') == 'true': hotlist_helpers.RemoveHotlist(mr.cnxn, mr.hotlist_id, self.services) return framework_helpers.FormatAbsoluteURL(mr, '/u/%s/hotlists' % mr.auth.email, saved=1, ts=int(time.time()), include_project=False) (summary, description, name, default_col_spec) = self._ParseMetaData(post_data, mr) is_private = post_data.get('is_private') != 'no' if not mr.errors.AnyErrors(): self.services.features.UpdateHotlist( mr.cnxn, mr.hotlist.hotlist_id, name=name, summary=summary, description=description, is_private=is_private, default_col_spec=default_col_spec) if mr.errors.AnyErrors(): self.PleaseCorrect(mr, initial_summary=summary, initial_description=description, initial_name=name, initial_default_col_spec=default_col_spec) else: return framework_helpers.FormatAbsoluteURL( mr, '/u/%s/hotlists/%s%s' % (mr.auth.user_id, mr.hotlist_id, urls.HOTLIST_DETAIL), saved=1, ts=int(time.time()), include_project=False)
def ProcessFormData(self, mr, post_data): """Process the posted form.""" viewed_user = mr.viewed_user_auth.user_pb viewed_user.email_bounce_timestamp = None self.services.user.UpdateUser(mr.cnxn, viewed_user.user_id, viewed_user) return framework_helpers.FormatAbsoluteURL( mr, mr.viewed_user_auth.user_view.profile_url, include_project=False, saved=1, ts=int(time.time()))
def ProcessFormData(self, mr, post_data): """Process the posted form.""" # 1. Gather data from the request. group_name = post_data.get('groupname') try: existing_group_id = self.services.user.LookupUserID(mr.cnxn, group_name) existing_settings = self.services.usergroup.GetGroupSettings( mr.cnxn, existing_group_id) if existing_settings: mr.errors.groupname = 'That user group already exists' except exceptions.NoSuchUserException: pass if post_data.get('import_group'): vis = usergroup_pb2.MemberVisibility.OWNERS ext_group_type = post_data.get('group_type') friend_projects = '' if not ext_group_type: mr.errors.groupimport = 'Please provide external group type' else: ext_group_type = str( usergroup_pb2.GroupType(int(ext_group_type))).lower() if (ext_group_type == 'computed' and not group_name.startswith('everyone@')): mr.errors.groupimport = 'Computed groups must be named everyone@' else: vis = usergroup_pb2.MemberVisibility(int(post_data['visibility'])) ext_group_type = None friend_projects = post_data.get('friendprojects', '') who_can_view_members = str(vis).lower() if not mr.errors.AnyErrors(): project_ids, error = self.services.usergroup.ValidateFriendProjects( mr.cnxn, self.services, friend_projects) if error: mr.errors.friendprojects = error # 2. Call services layer to save changes. if not mr.errors.AnyErrors(): group_id = self.services.usergroup.CreateGroup( mr.cnxn, self.services, group_name, who_can_view_members, ext_group_type, project_ids) # 3. Determine the next page in the UI flow. if mr.errors.AnyErrors(): self.PleaseCorrect(mr, initial_name=group_name) else: # Go to the new user group's detail page. return framework_helpers.FormatAbsoluteURL( mr, '/g/%s/' % group_id, include_project=False)
def ProcessFormData(self, mr, post_data): """Process the posted form.""" with work_env.WorkEnv(mr, self.services) as we: framework_helpers.UserSettings.ProcessSettingsForm( we, post_data, mr.auth.user_pb) url = framework_helpers.FormatAbsoluteURL(mr, urls.USER_SETTINGS, include_project=False, saved=1, ts=int(time.time())) return url
def _ProcessDeleteField(self, mr, config, field_def): """The user wants to delete the specified custom field definition.""" field_ids = [field_def.field_id] if field_def.field_type is tracker_pb2.FieldTypes.APPROVAL_TYPE: for fd in config.field_defs: if fd.approval_id == field_def.field_id: field_ids.append(fd.field_id) self.services.config.SoftDeleteFieldDefs(mr.cnxn, mr.project_id, field_ids) return framework_helpers.FormatAbsoluteURL(mr, urls.ADMIN_LABELS, deleted=1, ts=int(time.time()))
def ProcessFormData(self, mr, post_data): """Process the posted form.""" if not permissions.CanBan(mr, self.services): raise permissions.PermissionException( "You do not have permission to ban users.") framework_helpers.UserSettings.ProcessBanForm( mr.cnxn, self.services.user, post_data, mr.viewed_user_auth.user_id, mr.viewed_user_auth.user_pb) # TODO(jrobbins): Check all calls to FormatAbsoluteURL for include_project. return framework_helpers.FormatAbsoluteURL( mr, mr.viewed_user_auth.user_view.profile_url, include_project=False, saved=1, ts=int(time.time()))
def ProcessRemoveMembers(self, mr, post_data, hotlist_url): """Process the user's request to remove members.""" remove_strs = post_data.getall('remove') logging.info('remove_strs = %r', remove_strs) remove_ids = set( self.services.user.LookupUserIDs(mr.cnxn, remove_strs).values()) (owner_ids, editor_ids, follower_ids) = hotlist_helpers.MembersWithoutGivenIDs( mr.hotlist, remove_ids) self.services.features.UpdateHotlistRoles( mr.cnxn, mr.hotlist_id, owner_ids, editor_ids, follower_ids) return framework_helpers.FormatAbsoluteURL( mr, '%s%s' % ( hotlist_url, urls.HOTLIST_PEOPLE), saved=1, ts=int(time.time()), include_project=False)
def ProcessAddMembers(self, mr, post_data): """Process the user's request to add members. Args: mr: common information parsed from the HTTP request. post_data: dictionary of form data. Returns: String URL to redirect the user to after processing. """ # 1. Parse and validate user input. new_member_ids = project_helpers.ParseUsernames( mr.cnxn, self.services.user, post_data.get('addmembers')) role = post_data['role'] (owner_ids, committer_ids, contributor_ids) = project_helpers.MembersWithGivenIDs( mr.project, new_member_ids, role) total_people = len(owner_ids) + len(committer_ids) + len( contributor_ids) if total_people > framework_constants.MAX_PROJECT_PEOPLE: mr.errors.addmembers = ( 'Too many project members. The combined limit is %d.' % framework_constants.MAX_PROJECT_PEOPLE) # 2. Call services layer to save changes. if not mr.errors.AnyErrors(): self.services.project.UpdateProjectRoles(mr.cnxn, mr.project.project_id, owner_ids, committer_ids, contributor_ids) # 3. Determine the next page in the UI flow. if mr.errors.AnyErrors(): add_members_str = post_data.get('addmembers', '') self.PleaseCorrect(mr, initial_add_members=add_members_str, initially_expand_form=True) else: return framework_helpers.FormatAbsoluteURL( mr, urls.PEOPLE_LIST, saved=1, ts=int(time.time()), new=','.join([str(u) for u in new_member_ids]))
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. """ page_url = self.ProcessSubtabForm(post_data, mr) if page_url: return framework_helpers.FormatAbsoluteURL(mr, page_url, saved=1, ts=int(time.time()))
def ProcessFormData(self, mr, post_data): """Process the posted form.""" # 1. Gather data from the request. group_name = mr.viewed_username group_id = mr.viewed_user_auth.user_id if post_data.get('import_group'): vis_level = usergroup_pb2.MemberVisibility.OWNERS ext_group_type = post_data.get('group_type') friend_projects = '' if not ext_group_type: mr.errors.groupimport = 'Please provide external group type' else: ext_group_type = usergroup_pb2.GroupType(int(ext_group_type)) else: vis_level = post_data.get('visibility') ext_group_type = None friend_projects = post_data.get('friendprojects', '') if vis_level: vis_level = usergroup_pb2.MemberVisibility(int(vis_level)) else: mr.errors.groupimport = 'Cannot update settings for imported group' if not mr.errors.AnyErrors(): project_ids, error = self.services.usergroup.ValidateFriendProjects( mr.cnxn, self.services, friend_projects) if error: mr.errors.friendprojects = error # 2. Call services layer to save changes. if not mr.errors.AnyErrors(): group_settings = usergroup_pb2.UserGroupSettings( who_can_view_members=vis_level, ext_group_type=ext_group_type, friend_projects=project_ids) self.services.usergroup.UpdateSettings( mr.cnxn, group_id, group_settings) # 3. Determine the next page in the UI flow. if mr.errors.AnyErrors(): self.PleaseCorrect(mr, initial_name=group_name) else: return framework_helpers.FormatAbsoluteURL( mr, '/g/%s%s' % (group_name, urls.GROUP_ADMIN), include_project=False, saved=1, ts=int(time.time()))