Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(102)

Side by Side Diff: appengine/monorail/sitewide/projectcreate.py

Issue 1868553004: Open Source Monorail (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@master
Patch Set: Rebase Created 4 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « appengine/monorail/sitewide/moved.py ('k') | appengine/monorail/sitewide/projectsearch.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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': ''}
OLDNEW
« no previous file with comments | « appengine/monorail/sitewide/moved.py ('k') | appengine/monorail/sitewide/projectsearch.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698