blob: dff718a0b35ab4d7445b87dcdae9116e5cd54f40 [file] [log] [blame]
# Copyright 2016 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Page and form handlers for project administration "advanced" subtab.
The advanced subtab allows the project to be archived, unarchived, deleted, or
marked as moved. Site admins can use this page to "doom" a project, which is
basically archiving it in a way that cannot be reversed by the project owners.
The page also shows project data storage quota and usage values, and
site admins can edit those quotas.
"""
from __future__ import division
from __future__ import print_function
from __future__ import absolute_import
import time
import ezt
from businesslogic import work_env
from framework import framework_constants
from framework import framework_helpers
from framework import permissions
from framework import servlet
from framework import template_helpers
from framework import urls
from mrproto import project_pb2
from tracker import tracker_constants
class ProjectAdminAdvanced(servlet.Servlet):
"""A page with project state options for the Project Owner(s)."""
_PAGE_TEMPLATE = 'project/project-admin-advanced-page.ezt'
_MAIN_TAB_MODE = servlet.Servlet.MAIN_TAB_ADMIN
def AssertBasePermission(self, mr):
"""Make sure that the logged in user has permission to view this page.
Args:
mr: commonly used info parsed from the request.
"""
super(ProjectAdminAdvanced, self).AssertBasePermission(mr)
if not self.CheckPerm(mr, permissions.EDIT_PROJECT):
raise permissions.PermissionException(
'User is not allowed to administer this project')
def GatherPageData(self, mr):
"""Build up a dictionary of data values to use when rendering the page.
Args:
mr: commonly used info parsed from the request.
Returns:
Dict of values used by EZT for rendering the "Advanced" subtab.
"""
page_data = {
'admin_tab_mode': self.ADMIN_TAB_ADVANCED,
}
page_data.update(self._GatherPublishingOptions(mr))
page_data.update(self._GatherQuotaData(mr))
return page_data
def _GatherPublishingOptions(self, mr):
"""Gather booleans to control the publishing buttons to show in EZT."""
state = mr.project.state
offer_archive = state != project_pb2.ProjectState.ARCHIVED
offer_delete = state == project_pb2.ProjectState.ARCHIVED
offer_publish = (
state == project_pb2.ProjectState.ARCHIVED and
(self.CheckPerm(mr, permissions.PUBLISH_PROJECT) or
not mr.project.state_reason))
offer_move = state == project_pb2.ProjectState.LIVE
offer_doom = self.CheckPerm(mr, permissions.ADMINISTER_SITE)
moved_to = mr.project.moved_to or 'http://'
publishing_data = {
'offer_archive': ezt.boolean(offer_archive),
'offer_publish': ezt.boolean(offer_publish),
'offer_delete': ezt.boolean(offer_delete),
'offer_move': ezt.boolean(offer_move),
'moved_to': moved_to,
'offer_doom': ezt.boolean(offer_doom),
'default_doom_reason': framework_constants.DEFAULT_DOOM_REASON,
}
return publishing_data
def _GatherQuotaData(self, mr):
"""Gather quota info from backends so that it can be passed to EZT."""
offer_quota_editing = self.CheckPerm(mr, permissions.EDIT_QUOTA)
quota_data = {
'offer_quota_editing': ezt.boolean(offer_quota_editing),
'attachment_quota': self._BuildAttachmentQuotaData(mr.project),
}
return quota_data
def _BuildComponentQuota(self, used_bytes, quota_bytes, field_name):
"""Return an object to easily display quota info in EZT."""
if quota_bytes:
used_percent = 100 * used_bytes // quota_bytes
else:
used_percent = 0
quota_mb = quota_bytes // 1024 // 1024
return template_helpers.EZTItem(
used=template_helpers.BytesKbOrMb(used_bytes),
quota_mb=quota_mb,
used_percent=used_percent,
avail_percent=100 - used_percent,
field_name=field_name)
def _BuildAttachmentQuotaData(self, project):
return self._BuildComponentQuota(
project.attachment_bytes_used,
project.attachment_quota or
tracker_constants.ISSUE_ATTACHMENTS_QUOTA_HARD,
'attachment_quota_mb')
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 _ProcessQuota(self, mr, post_data):
"""Process form data to update project quotas."""
if not self.CheckPerm(mr, permissions.EDIT_QUOTA):
raise permissions.PermissionException(
'User is not allowed to change project quotas')
try:
new_attachment_quota = int(post_data['attachment_quota_mb'])
new_attachment_quota *= 1024 * 1024
except ValueError:
mr.errors.attachment_quota = 'Invalid value'
self.PleaseCorrect(mr) # Don't echo back the bad input, just start over.
return
with work_env.WorkEnv(mr, self.services) as we:
we.UpdateProject(
mr.project.project_id, attachment_quota=new_attachment_quota)
def _ProcessPublishingOptions(self, mr, post_data):
"""Process form data to update project state."""
# Note that EDIT_PROJECT is the base permission for this servlet, but
# dooming and undooming projects also requires PUBLISH_PROJECT.
state = mr.project.state
with work_env.WorkEnv(mr, self.services) as we:
if 'archivebtn' in post_data and not mr.project.delete_time:
we.UpdateProject(
mr.project.project_id, state=project_pb2.ProjectState.ARCHIVED)
elif 'deletebtn' in post_data: # Mark the project for immediate deletion.
if state != project_pb2.ProjectState.ARCHIVED:
raise permissions.PermissionException(
'Projects must be archived before being deleted')
we.DeleteProject(mr.project_id)
elif 'doombtn' in post_data: # Go from any state to forced ARCHIVED.
if not self.CheckPerm(mr, permissions.PUBLISH_PROJECT):
raise permissions.PermissionException(
'User is not allowed to doom projects')
reason = post_data.get('reason')
delete_time = time.time() + framework_constants.DEFAULT_DOOM_PERIOD
we.UpdateProject(
mr.project.project_id, state=project_pb2.ProjectState.ARCHIVED,
state_reason=reason, delete_time=delete_time)
elif 'publishbtn' in post_data: # Go from any state to LIVE
if (mr.project.delete_time and
not self.CheckPerm(mr, permissions.PUBLISH_PROJECT)):
raise permissions.PermissionException(
'User is not allowed to unarchive doomed projects')
we.UpdateProject(
mr.project.project_id, state=project_pb2.ProjectState.LIVE,
state_reason='', delete_time=0, read_only_reason='')
elif 'movedbtn' in post_data: # Record the moved_to location.
if state != project_pb2.ProjectState.LIVE:
raise permissions.PermissionException(
'This project is not live, no user can move it')
moved_to = post_data.get('moved_to', '')
we.UpdateProject(mr.project.project_id, moved_to=moved_to)
def GetProjectAdminAdvancedPage(self, **kwargs):
return self.handler(**kwargs)
def PostProjectAdminAdvancedPage(self, **kwargs):
return self.handler(**kwargs)