Merge branch 'main' into avm99963-monorail
Merged commit 4137ed7879acadbf891e8c471108acb874dae886.
GitOrigin-RevId: b6100ffc5b1da355a35f37b13fcaaf746ee8b307
diff --git a/framework/servlet_helpers.py b/framework/servlet_helpers.py
index 68eb0c4..89fe587 100644
--- a/framework/servlet_helpers.py
+++ b/framework/servlet_helpers.py
@@ -8,28 +8,83 @@
from __future__ import division
from __future__ import absolute_import
+import settings
import calendar
import datetime
import logging
import urllib
+import time
+from framework import framework_constants
from framework import framework_bizobj
from framework import framework_helpers
from framework import permissions
from framework import template_helpers
from framework import urls
from framework import xsrf
+from proto import project_pb2
+
+from google.appengine.api import app_identity
+from google.appengine.api import modules
+from google.appengine.api import users
_ZERO = datetime.timedelta(0)
+
+class MethodNotSupportedError(NotImplementedError):
+ """An exception class for indicating that the method is not supported.
+
+ Used by GatherPageData and ProcessFormData in Servlet.
+ """
+ pass
+
+
+class _ContextDebugItem(object):
+ """Wrapper class to generate on-screen debugging output."""
+
+ def __init__(self, key, val):
+ """Store the key and generate a string for the value."""
+ self.key = key
+ if isinstance(val, list):
+ nested_debug_strs = [self.StringRep(v) for v in val]
+ self.val = '[%s]' % ', '.join(nested_debug_strs)
+ else:
+ self.val = self.StringRep(val)
+
+ def StringRep(self, val):
+ """Make a useful string representation of the given value."""
+ try:
+ return val.DebugString()
+ except Exception:
+ try:
+ return str(val.__dict__)
+ except Exception:
+ return repr(val)
+
+
+class ContextDebugCollection(object):
+ """Attach a title to a dictionary for exporting as a table of debug info."""
+
+ def __init__(self, title, collection):
+ self.title = title
+ self.collection = [
+ _ContextDebugItem(key, collection[key])
+ for key in sorted(collection.keys())
+ ]
+
+
class _UTCTimeZone(datetime.tzinfo):
- """UTC"""
- def utcoffset(self, _dt):
- return _ZERO
- def tzname(self, _dt):
- return "UTC"
- def dst(self, _dt):
- return _ZERO
+ """UTC"""
+
+ def utcoffset(self, _dt):
+ return _ZERO
+
+ def tzname(self, _dt):
+ return "UTC"
+
+ def dst(self, _dt):
+ return _ZERO
+
_UTC = _UTCTimeZone()
@@ -132,21 +187,11 @@
case. Otherewise it will be a fully qualified URL that includes some
query string parameters.
"""
+ # TODO: remove the custom_issue_entry_url since its no longer
if not config.custom_issue_entry_url:
return '/p/%s/issues/entry' % (mr.project_name)
- base_url = config.custom_issue_entry_url
- sep = '&' if '?' in base_url else '?'
- token = xsrf.GenerateToken(
- mr.auth.user_id, '/p/%s%s%s' % (mr.project_name, urls.ISSUE_ENTRY, '.do'))
- role_name = framework_helpers.GetRoleName(mr.auth.effective_ids, mr.project)
-
- continue_url = urllib.quote(framework_helpers.FormatAbsoluteURL(
- mr, urls.ISSUE_ENTRY + '.do'))
-
- return '%s%stoken=%s&role=%s&continue=%s' % (
- base_url, sep, urllib.quote(token),
- urllib.quote(role_name or ''), continue_url)
+ return '/p/chromium/issues/wizard'
def IssueListURL(mr, config, query_string=None):
@@ -158,3 +203,76 @@
if config and config.member_default_query:
url += '?q=' + urllib.quote_plus(config.member_default_query)
return url
+
+
+def ProjectIsRestricted(mr):
+ """Return True if the mr has a 'private' project."""
+ return (mr.project and mr.project.access != project_pb2.ProjectAccess.ANYONE)
+
+
+def SafeCreateLoginURL(mr, continue_url=None):
+ """Make a login URL w/ a detailed continue URL, otherwise use a short one."""
+ continue_url = continue_url or mr.current_page_url
+ try:
+ url = users.create_login_url(continue_url)
+ except users.RedirectTooLongError:
+ if mr.project_name:
+ url = users.create_login_url('/p/%s' % mr.project_name)
+ else:
+ url = users.create_login_url('/')
+
+ # Give the user a choice of existing accounts in their session
+ # or the option to add an account, even if they are currently
+ # signed in to exactly one account.
+ if mr.auth.user_id:
+ # Notice: this makes assuptions about the output of users.create_login_url,
+ # which can change at any time. See https://crbug.com/monorail/3352.
+ url = url.replace('/ServiceLogin', '/AccountChooser', 1)
+ return url
+
+
+def SafeCreateLogoutURL(mr):
+ """Make a logout URL w/ a detailed continue URL, otherwise use a short one."""
+ try:
+ return users.create_logout_url(mr.current_page_url)
+ except users.RedirectTooLongError:
+ if mr.project_name:
+ return users.create_logout_url('/p/%s' % mr.project_name)
+ else:
+ return users.create_logout_url('/')
+
+
+def VersionBaseURL(request):
+ """Return a version-specific URL that we use to load static assets."""
+ if settings.local_mode:
+ version_base = '%s://%s' % (request.scheme, request.host)
+ else:
+ version_base = '%s://%s-dot-%s' % (
+ request.scheme, modules.get_current_version_name(),
+ app_identity.get_default_version_hostname())
+
+ return version_base
+
+
+def CalcProjectAlert(project):
+ """Return a string to be shown as red text explaining the project state."""
+
+ project_alert = None
+
+ if project.read_only_reason:
+ project_alert = 'READ-ONLY: %s.' % project.read_only_reason
+ if project.moved_to:
+ project_alert = 'This project has moved to: %s.' % project.moved_to
+ elif project.delete_time:
+ delay_seconds = project.delete_time - time.time()
+ delay_days = delay_seconds // framework_constants.SECS_PER_DAY
+ if delay_days <= 0:
+ project_alert = 'Scheduled for deletion today.'
+ else:
+ days_word = 'day' if delay_days == 1 else 'days'
+ project_alert = (
+ 'Scheduled for deletion in %d %s.' % (delay_days, days_word))
+ elif project.state == project_pb2.ProjectState.ARCHIVED:
+ project_alert = 'Project is archived: read-only by members only.'
+
+ return project_alert