blob: f6c384265c7133aaee1f86e8e18e163b2272ff18 [file] [log] [blame]
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +01001# Copyright 2016 The Chromium Authors
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
Copybara854996b2021-09-07 19:36:02 +00004
5"""Classes for users to create a new project."""
6from __future__ import print_function
7from __future__ import division
8from __future__ import absolute_import
9
10
11import logging
12from six import string_types
13import ezt
14
15import settings
16from businesslogic import work_env
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010017from framework import exceptions
Copybara854996b2021-09-07 19:36:02 +000018from framework import filecontent
19from framework import framework_helpers
20from framework import gcs_helpers
21from framework import jsonfeed
22from framework import permissions
23from framework import servlet
24from framework import urls
25from project import project_constants
26from project import project_helpers
27from project import project_views
28from services import project_svc
29from tracker import tracker_bizobj
30from tracker import tracker_views
31
32
33_MSG_PROJECT_NAME_NOT_AVAIL = 'That project name is not available.'
34_MSG_MISSING_PROJECT_NAME = 'Missing project name'
35_MSG_INVALID_PROJECT_NAME = 'Invalid project name'
36_MSG_MISSING_PROJECT_SUMMARY = 'Missing project summary'
37
38
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010039class ProjectCreate(servlet.Servlet):
Copybara854996b2021-09-07 19:36:02 +000040 """Shows a page with a simple form to create a project."""
41
42 _PAGE_TEMPLATE = 'sitewide/project-create-page.ezt'
43
44 def AssertBasePermission(self, mr):
45 """Assert that the user has the permissions needed to view this page."""
46 super(ProjectCreate, self).AssertBasePermission(mr)
47
48 if not permissions.CanCreateProject(mr.perms):
49 raise permissions.PermissionException(
50 'User is not allowed to create a project')
51
52 def GatherPageData(self, _mr):
53 """Build up a dictionary of data values to use when rendering the page."""
54 available_access_levels = project_helpers.BuildProjectAccessOptions(None)
55 offer_access_level = len(available_access_levels) > 1
56 if settings.default_access_level:
57 access_view = project_views.ProjectAccessView(
58 settings.default_access_level)
59 else:
60 access_view = None
61
62 return {
63 'initial_name': '',
64 'initial_summary': '',
65 'initial_description': '',
66 'initial_project_home': '',
67 'initial_docs_url': '',
68 'initial_source_url': '',
69 'initial_logo_gcs_id': '',
70 'initial_logo_file_name': '',
71 'logo_view': tracker_views.LogoView(None),
72 'labels': [],
73 'max_project_name_length': project_constants.MAX_PROJECT_NAME_LENGTH,
74 'offer_access_level': ezt.boolean(offer_access_level),
75 'initial_access': access_view,
76 'available_access_levels': available_access_levels,
77 }
78
79 def ProcessFormData(self, mr, post_data):
80 """Process the posted form."""
81 # 1. Parse and validate user input.
82 # Project name is taken from post_data because we are creating it.
83 project_name = post_data.get('projectname')
84 if not project_name:
85 mr.errors.projectname = _MSG_MISSING_PROJECT_NAME
86 elif not project_helpers.IsValidProjectName(project_name):
87 mr.errors.projectname = _MSG_INVALID_PROJECT_NAME
88
89 summary = post_data.get('summary')
90 if not summary:
91 mr.errors.summary = _MSG_MISSING_PROJECT_SUMMARY
92 description = post_data.get('description', '')
93
94 access = project_helpers.ParseProjectAccess(None, post_data.get('access'))
95 home_page = post_data.get('project_home')
96 if home_page and not (
97 home_page.startswith('http://') or home_page.startswith('https://')):
98 mr.errors.project_home = 'Home page link must start with http(s)://'
99 docs_url = post_data.get('docs_url')
100 if docs_url and not (
101 docs_url.startswith('http:') or docs_url.startswith('https:')):
102 mr.errors.docs_url = 'Documentation link must start with http: or https:'
103
104 # These are not specified on via the ProjectCreate form,
105 # the user must edit the project after creation to set them.
106 committer_ids = []
107 contributor_ids = []
108
109 # Validate that provided logo is supported.
110 logo_provided = 'logo' in post_data and not isinstance(
111 post_data['logo'], string_types)
112 if logo_provided:
113 item = post_data['logo']
114 try:
115 gcs_helpers.CheckMimeTypeResizable(
116 filecontent.GuessContentTypeFromFilename(item.filename))
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100117 except gcs_helpers.UnsupportedMimeType as e:
118 mr.errors.logo = str(e)
Copybara854996b2021-09-07 19:36:02 +0000119
120 # 2. Call services layer to save changes.
121 if not mr.errors.AnyErrors():
122 with work_env.WorkEnv(mr, self.services) as we:
123 try:
124 project_id = we.CreateProject(
125 project_name, [mr.auth.user_id],
126 committer_ids, contributor_ids, summary, description,
127 access=access, home_page=home_page, docs_url=docs_url)
128
129 config = tracker_bizobj.MakeDefaultProjectIssueConfig(project_id)
130 self.services.config.StoreConfig(mr.cnxn, config)
131 # Note: No need to store any canned queries or rules yet.
132 self.services.issue.InitializeLocalID(mr.cnxn, project_id)
133
134 # Update project with logo if specified.
135 if logo_provided:
136 item = post_data['logo']
137 logo_file_name = item.filename
138 logo_gcs_id = gcs_helpers.StoreLogoInGCS(
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100139 logo_file_name, item.read(), project_id)
Copybara854996b2021-09-07 19:36:02 +0000140 we.UpdateProject(
141 project_id, logo_gcs_id=logo_gcs_id,
142 logo_file_name=logo_file_name)
143
144 except exceptions.ProjectAlreadyExists:
145 mr.errors.projectname = _MSG_PROJECT_NAME_NOT_AVAIL
146
147 # 3. Determine the next page in the UI flow.
148 if mr.errors.AnyErrors():
149 access_view = project_views.ProjectAccessView(access)
150 self.PleaseCorrect(
151 mr, initial_summary=summary, initial_description=description,
152 initial_name=project_name, initial_access=access_view)
153 else:
154 # Go to the new project's introduction page.
155 return framework_helpers.FormatAbsoluteURL(
156 mr, urls.ADMIN_INTRO, project_name=project_name)
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +0200157
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +0200158 def GetCreateProject(self, **kwargs):
159 return self.handler(**kwargs)
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +0200160
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +0200161 def PostCreateProject(self, **kwargs):
162 return self.handler(**kwargs)