OLD | NEW |
(Empty) | |
| 1 # Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is govered by a BSD-style |
| 3 # license that can be found in the LICENSE file or at |
| 4 # https://developers.google.com/open-source/licenses/bsd |
| 5 |
| 6 """Classes for users to create a new project.""" |
| 7 |
| 8 |
| 9 import logging |
| 10 from third_party import ezt |
| 11 |
| 12 import settings |
| 13 from framework import actionlimit |
| 14 from framework import filecontent |
| 15 from framework import framework_bizobj |
| 16 from framework import framework_constants |
| 17 from framework import framework_helpers |
| 18 from framework import gcs_helpers |
| 19 from framework import jsonfeed |
| 20 from framework import permissions |
| 21 from framework import servlet |
| 22 from framework import urls |
| 23 from project import project_helpers |
| 24 from project import project_views |
| 25 from services import project_svc |
| 26 from tracker import tracker_bizobj |
| 27 from tracker import tracker_views |
| 28 |
| 29 |
| 30 _MSG_PROJECT_NAME_NOT_AVAIL = 'That project name is not available.' |
| 31 _MSG_MISSING_PROJECT_NAME = 'Missing project name' |
| 32 _MSG_INVALID_PROJECT_NAME = 'Invalid project name' |
| 33 _MSG_MISSING_PROJECT_SUMMARY = 'Missing project summary' |
| 34 |
| 35 |
| 36 class ProjectCreate(servlet.Servlet): |
| 37 """Shows a page with a simple form to create a project.""" |
| 38 |
| 39 _PAGE_TEMPLATE = 'sitewide/project-create-page.ezt' |
| 40 |
| 41 _CAPTCHA_ACTION_TYPES = [actionlimit.PROJECT_CREATION] |
| 42 |
| 43 def AssertBasePermission(self, mr): |
| 44 """Assert that the user has the permissions needed to view this page.""" |
| 45 super(ProjectCreate, self).AssertBasePermission(mr) |
| 46 |
| 47 if not permissions.CanCreateProject(mr.perms): |
| 48 raise permissions.PermissionException( |
| 49 'User is not allowed to create a project') |
| 50 |
| 51 def GatherPageData(self, _mr): |
| 52 """Build up a dictionary of data values to use when rendering the page.""" |
| 53 available_access_levels = project_helpers.BuildProjectAccessOptions(None) |
| 54 offer_access_level = len(available_access_levels) > 1 |
| 55 if settings.default_access_level: |
| 56 access_view = project_views.ProjectAccessView( |
| 57 settings.default_access_level) |
| 58 else: |
| 59 access_view = None |
| 60 |
| 61 return { |
| 62 'initial_name': '', |
| 63 'initial_summary': '', |
| 64 'initial_description': '', |
| 65 'initial_project_home': '', |
| 66 'initial_docs_url': '', |
| 67 'initial_logo_gcs_id': '', |
| 68 'initial_logo_file_name': '', |
| 69 'logo_view': tracker_views.LogoView(None), |
| 70 'labels': [], |
| 71 'max_project_name_length': framework_constants.MAX_PROJECT_NAME_LENGTH, |
| 72 'offer_access_level': ezt.boolean(offer_access_level), |
| 73 'initial_access': access_view, |
| 74 'available_access_levels': available_access_levels, |
| 75 } |
| 76 |
| 77 def GatherHelpData(self, mr, _page_data): |
| 78 """Return a dict of values to drive on-page user help. |
| 79 |
| 80 Args: |
| 81 mr: common information parsed from the HTTP request. |
| 82 _page_data: Dictionary of base and page template data. |
| 83 |
| 84 Returns: |
| 85 A dict of values to drive on-page user help, to be added to page_data. |
| 86 """ |
| 87 cue_remaining_projects = None |
| 88 |
| 89 (_period, _soft, _hard, |
| 90 life_max) = actionlimit.ACTION_LIMITS[actionlimit.PROJECT_CREATION] |
| 91 actionlimit_pb = actionlimit.GetLimitPB( |
| 92 mr.auth.user_pb, actionlimit.PROJECT_CREATION) |
| 93 if actionlimit_pb.get_assigned_value('lifetime_limit'): |
| 94 life_max = actionlimit_pb.lifetime_limit |
| 95 if life_max is not None: |
| 96 if (actionlimit_pb.lifetime_count + 10 >= life_max |
| 97 and actionlimit_pb.lifetime_count < life_max): |
| 98 cue_remaining_projects = life_max - actionlimit_pb.lifetime_count |
| 99 |
| 100 return { |
| 101 'cue': None, |
| 102 'cue_remaining_projects': cue_remaining_projects, |
| 103 } |
| 104 |
| 105 def ProcessFormData(self, mr, post_data): |
| 106 """Process the posted form.""" |
| 107 # 1. Parse and validate user input. |
| 108 # Project name is taken from post_data because we are creating it. |
| 109 project_name = post_data.get('projectname') |
| 110 if not project_name: |
| 111 mr.errors.projectname = _MSG_MISSING_PROJECT_NAME |
| 112 elif not framework_bizobj.IsValidProjectName(project_name): |
| 113 mr.errors.projectname = _MSG_INVALID_PROJECT_NAME |
| 114 |
| 115 summary = post_data.get('summary') |
| 116 if not summary: |
| 117 mr.errors.summary = _MSG_MISSING_PROJECT_SUMMARY |
| 118 description = post_data.get('description', '') |
| 119 |
| 120 access = project_helpers.ParseProjectAccess(None, post_data.get('access')) |
| 121 home_page = post_data.get('project_home') |
| 122 if home_page and not ( |
| 123 home_page.startswith('http://') or home_page.startswith('https://')): |
| 124 mr.errors.project_home = 'Home page link must start with http(s)://' |
| 125 docs_url = post_data.get('docs_url') |
| 126 if docs_url and not ( |
| 127 docs_url.startswith('http:') or docs_url.startswith('https:')): |
| 128 mr.errors.docs_url = 'Documentation link must start with http: or https:' |
| 129 |
| 130 self.CheckCaptcha(mr, post_data) |
| 131 |
| 132 # These are not specified on via the ProjectCreate form, |
| 133 # the user must edit the project after creation to set them. |
| 134 committer_ids = [] |
| 135 contributor_ids = [] |
| 136 |
| 137 # Validate that provided logo is supported. |
| 138 logo_provided = 'logo' in post_data and not isinstance( |
| 139 post_data['logo'], basestring) |
| 140 if logo_provided: |
| 141 item = post_data['logo'] |
| 142 try: |
| 143 gcs_helpers.CheckMimeTypeResizable( |
| 144 filecontent.GuessContentTypeFromFilename(item.filename)) |
| 145 except gcs_helpers.UnsupportedMimeType, e: |
| 146 mr.errors.logo = e.message |
| 147 |
| 148 # 2. Call services layer to save changes. |
| 149 if not mr.errors.AnyErrors(): |
| 150 try: |
| 151 project_id = self.services.project.CreateProject( |
| 152 mr.cnxn, project_name, [mr.auth.user_id], |
| 153 committer_ids, contributor_ids, summary, description, |
| 154 access=access, home_page=home_page, docs_url=docs_url) |
| 155 |
| 156 config = tracker_bizobj.MakeDefaultProjectIssueConfig(project_id) |
| 157 self.services.config.StoreConfig(mr.cnxn, config) |
| 158 # Note: No need to store any canned queries or rules yet. |
| 159 self.services.issue.InitializeLocalID(mr.cnxn, project_id) |
| 160 |
| 161 # Update project with logo if specified. |
| 162 if logo_provided: |
| 163 item = post_data['logo'] |
| 164 logo_file_name = item.filename |
| 165 logo_gcs_id = gcs_helpers.StoreLogoInGCS( |
| 166 logo_file_name, item.value, project_id) |
| 167 self.services.project.UpdateProject( |
| 168 mr.cnxn, project_id, logo_gcs_id=logo_gcs_id, |
| 169 logo_file_name=logo_file_name) |
| 170 |
| 171 self.CountRateLimitedActions( |
| 172 mr, {actionlimit.PROJECT_CREATION: 1}) |
| 173 except project_svc.ProjectAlreadyExists: |
| 174 mr.errors.projectname = _MSG_PROJECT_NAME_NOT_AVAIL |
| 175 |
| 176 # 3. Determine the next page in the UI flow. |
| 177 if mr.errors.AnyErrors(): |
| 178 access_view = project_views.ProjectAccessView(access) |
| 179 self.PleaseCorrect( |
| 180 mr, initial_summary=summary, initial_description=description, |
| 181 initial_name=project_name, initial_access=access_view) |
| 182 else: |
| 183 # Go to the new project's introduction page. |
| 184 return framework_helpers.FormatAbsoluteURL( |
| 185 mr, urls.ADMIN_INTRO, project_name=project_name) |
| 186 |
| 187 |
| 188 class CheckProjectNameJSON(jsonfeed.JsonFeed): |
| 189 """JSON data for handling project name checks when creating a project.""" |
| 190 |
| 191 def HandleRequest(self, mr): |
| 192 """Provide the UI with info about the availability of the project name. |
| 193 |
| 194 Args: |
| 195 mr: common information parsed from the HTTP request. |
| 196 |
| 197 Returns: |
| 198 Results dictionary in JSON format. |
| 199 """ |
| 200 if self.services.project.LookupProjectIDs(mr.cnxn, [mr.specified_project]): |
| 201 return {'error_message': _MSG_PROJECT_NAME_NOT_AVAIL} |
| 202 |
| 203 return {'error_message': ''} |
OLD | NEW |