blob: dff718a0b35ab4d7445b87dcdae9116e5cd54f40 [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"""Page and form handlers for project administration "advanced" subtab.
6
7The advanced subtab allows the project to be archived, unarchived, deleted, or
8marked as moved. Site admins can use this page to "doom" a project, which is
9basically archiving it in a way that cannot be reversed by the project owners.
10
11The page also shows project data storage quota and usage values, and
12site admins can edit those quotas.
13"""
14
15from __future__ import division
16from __future__ import print_function
17from __future__ import absolute_import
18
Copybara854996b2021-09-07 19:36:02 +000019import time
20
21import ezt
22
23from businesslogic import work_env
24from framework import framework_constants
25from framework import framework_helpers
26from framework import permissions
27from framework import servlet
28from framework import template_helpers
29from framework import urls
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010030from mrproto import project_pb2
Copybara854996b2021-09-07 19:36:02 +000031from tracker import tracker_constants
32
33
34class ProjectAdminAdvanced(servlet.Servlet):
35 """A page with project state options for the Project Owner(s)."""
36
37 _PAGE_TEMPLATE = 'project/project-admin-advanced-page.ezt'
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010038 _MAIN_TAB_MODE = servlet.Servlet.MAIN_TAB_ADMIN
Copybara854996b2021-09-07 19:36:02 +000039
40 def AssertBasePermission(self, mr):
41 """Make sure that the logged in user has permission to view this page.
42
43 Args:
44 mr: commonly used info parsed from the request.
45 """
46 super(ProjectAdminAdvanced, self).AssertBasePermission(mr)
47 if not self.CheckPerm(mr, permissions.EDIT_PROJECT):
48 raise permissions.PermissionException(
49 'User is not allowed to administer this project')
50
51 def GatherPageData(self, mr):
52 """Build up a dictionary of data values to use when rendering the page.
53
54 Args:
55 mr: commonly used info parsed from the request.
56
57 Returns:
58 Dict of values used by EZT for rendering the "Advanced" subtab.
59 """
60 page_data = {
61 'admin_tab_mode': self.ADMIN_TAB_ADVANCED,
62 }
63 page_data.update(self._GatherPublishingOptions(mr))
64 page_data.update(self._GatherQuotaData(mr))
65
66 return page_data
67
68 def _GatherPublishingOptions(self, mr):
69 """Gather booleans to control the publishing buttons to show in EZT."""
70 state = mr.project.state
71 offer_archive = state != project_pb2.ProjectState.ARCHIVED
72 offer_delete = state == project_pb2.ProjectState.ARCHIVED
73 offer_publish = (
74 state == project_pb2.ProjectState.ARCHIVED and
75 (self.CheckPerm(mr, permissions.PUBLISH_PROJECT) or
76 not mr.project.state_reason))
77 offer_move = state == project_pb2.ProjectState.LIVE
78 offer_doom = self.CheckPerm(mr, permissions.ADMINISTER_SITE)
79 moved_to = mr.project.moved_to or 'http://'
80
81 publishing_data = {
82 'offer_archive': ezt.boolean(offer_archive),
83 'offer_publish': ezt.boolean(offer_publish),
84 'offer_delete': ezt.boolean(offer_delete),
85 'offer_move': ezt.boolean(offer_move),
86 'moved_to': moved_to,
87 'offer_doom': ezt.boolean(offer_doom),
88 'default_doom_reason': framework_constants.DEFAULT_DOOM_REASON,
89 }
90
91 return publishing_data
92
93 def _GatherQuotaData(self, mr):
94 """Gather quota info from backends so that it can be passed to EZT."""
95 offer_quota_editing = self.CheckPerm(mr, permissions.EDIT_QUOTA)
96
97 quota_data = {
98 'offer_quota_editing': ezt.boolean(offer_quota_editing),
99 'attachment_quota': self._BuildAttachmentQuotaData(mr.project),
100 }
101
102 return quota_data
103
104 def _BuildComponentQuota(self, used_bytes, quota_bytes, field_name):
105 """Return an object to easily display quota info in EZT."""
106 if quota_bytes:
107 used_percent = 100 * used_bytes // quota_bytes
108 else:
109 used_percent = 0
110
111 quota_mb = quota_bytes // 1024 // 1024
112
113 return template_helpers.EZTItem(
114 used=template_helpers.BytesKbOrMb(used_bytes),
115 quota_mb=quota_mb,
116 used_percent=used_percent,
117 avail_percent=100 - used_percent,
118 field_name=field_name)
119
120 def _BuildAttachmentQuotaData(self, project):
121 return self._BuildComponentQuota(
122 project.attachment_bytes_used,
123 project.attachment_quota or
124 tracker_constants.ISSUE_ATTACHMENTS_QUOTA_HARD,
125 'attachment_quota_mb')
126
127 def ProcessFormData(self, mr, post_data):
128 """Process the posted form.
129
130 Args:
131 mr: commonly used info parsed from the request.
132 post_data: dictionary of HTML form data.
133
134 Returns:
135 String URL to redirect to after processing is completed.
136 """
137 if 'savechanges' in post_data:
138 self._ProcessQuota(mr, post_data)
139 else:
140 self._ProcessPublishingOptions(mr, post_data)
141
142 if 'deletebtn' in post_data:
143 url = framework_helpers.FormatAbsoluteURL(
144 mr, urls.HOSTING_HOME, include_project=False)
145 else:
146 url = framework_helpers.FormatAbsoluteURL(
147 mr, urls.ADMIN_ADVANCED, saved=1, ts=int(time.time()))
148
149 return url
150
151 def _ProcessQuota(self, mr, post_data):
152 """Process form data to update project quotas."""
153 if not self.CheckPerm(mr, permissions.EDIT_QUOTA):
154 raise permissions.PermissionException(
155 'User is not allowed to change project quotas')
156
157 try:
158 new_attachment_quota = int(post_data['attachment_quota_mb'])
159 new_attachment_quota *= 1024 * 1024
160 except ValueError:
161 mr.errors.attachment_quota = 'Invalid value'
162 self.PleaseCorrect(mr) # Don't echo back the bad input, just start over.
163 return
164
165 with work_env.WorkEnv(mr, self.services) as we:
166 we.UpdateProject(
167 mr.project.project_id, attachment_quota=new_attachment_quota)
168
169 def _ProcessPublishingOptions(self, mr, post_data):
170 """Process form data to update project state."""
171 # Note that EDIT_PROJECT is the base permission for this servlet, but
172 # dooming and undooming projects also requires PUBLISH_PROJECT.
173
174 state = mr.project.state
175
176 with work_env.WorkEnv(mr, self.services) as we:
177 if 'archivebtn' in post_data and not mr.project.delete_time:
178 we.UpdateProject(
179 mr.project.project_id, state=project_pb2.ProjectState.ARCHIVED)
180
181 elif 'deletebtn' in post_data: # Mark the project for immediate deletion.
182 if state != project_pb2.ProjectState.ARCHIVED:
183 raise permissions.PermissionException(
184 'Projects must be archived before being deleted')
185 we.DeleteProject(mr.project_id)
186
187 elif 'doombtn' in post_data: # Go from any state to forced ARCHIVED.
188 if not self.CheckPerm(mr, permissions.PUBLISH_PROJECT):
189 raise permissions.PermissionException(
190 'User is not allowed to doom projects')
191 reason = post_data.get('reason')
192 delete_time = time.time() + framework_constants.DEFAULT_DOOM_PERIOD
193 we.UpdateProject(
194 mr.project.project_id, state=project_pb2.ProjectState.ARCHIVED,
195 state_reason=reason, delete_time=delete_time)
196
197 elif 'publishbtn' in post_data: # Go from any state to LIVE
198 if (mr.project.delete_time and
199 not self.CheckPerm(mr, permissions.PUBLISH_PROJECT)):
200 raise permissions.PermissionException(
201 'User is not allowed to unarchive doomed projects')
202 we.UpdateProject(
203 mr.project.project_id, state=project_pb2.ProjectState.LIVE,
204 state_reason='', delete_time=0, read_only_reason='')
205
206 elif 'movedbtn' in post_data: # Record the moved_to location.
207 if state != project_pb2.ProjectState.LIVE:
208 raise permissions.PermissionException(
209 'This project is not live, no user can move it')
210 moved_to = post_data.get('moved_to', '')
211 we.UpdateProject(mr.project.project_id, moved_to=moved_to)
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +0200212
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100213 def GetProjectAdminAdvancedPage(self, **kwargs):
214 return self.handler(**kwargs)
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +0200215
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100216 def PostProjectAdminAdvancedPage(self, **kwargs):
217 return self.handler(**kwargs)