Merge branch 'main' into avm99963-monorail

Merged commit cd4b3b336f1f14afa02990fdc2eec5d9467a827e

GitOrigin-RevId: e67bbf185d5538e1472bb42e0abb2a141f88bac1
diff --git a/sitewide/custom_404.py b/sitewide/custom_404.py
index 397bd1d..557d1ff 100644
--- a/sitewide/custom_404.py
+++ b/sitewide/custom_404.py
@@ -15,8 +15,9 @@
 from __future__ import division
 from __future__ import absolute_import
 
-import httplib
+from six.moves import http_client
 from framework import exceptions
+from framework import flaskservlet
 from framework import servlet
 
 
@@ -37,5 +38,8 @@
     if not mr.project_name:
       raise exceptions.InputException('No project specified')
     return {
-      'http_response_code': httplib.NOT_FOUND,
-      }
+        'http_response_code': http_client.NOT_FOUND,
+    }
+
+  # def Get404Page(self, **kwargs):
+  #   return self.handler(**kwargs)
diff --git a/sitewide/groupadmin.py b/sitewide/groupadmin.py
index 32ba007..3e8bcce 100644
--- a/sitewide/groupadmin.py
+++ b/sitewide/groupadmin.py
@@ -13,6 +13,7 @@
 
 import ezt
 
+from framework import flaskservlet
 from framework import framework_helpers
 from framework import permissions
 from framework import servlet
@@ -121,3 +122,9 @@
       return framework_helpers.FormatAbsoluteURL(
           mr, '/g/%s%s' % (group_name, urls.GROUP_ADMIN),
           include_project=False, saved=1, ts=int(time.time()))
+
+  # def GetGroupAdmin(self, **kwargs):
+  #   return self.handler(**kwargs)
+
+  # def PostGroupAdmin(self, **kwargs):
+  #   return self.handler(**kwargs)
diff --git a/sitewide/groupcreate.py b/sitewide/groupcreate.py
index 2dac146..ce0f151 100644
--- a/sitewide/groupcreate.py
+++ b/sitewide/groupcreate.py
@@ -11,7 +11,7 @@
 import logging
 import re
 
-from framework import exceptions
+from framework import exceptions, flaskservlet
 from framework import framework_helpers
 from framework import permissions
 from framework import servlet
@@ -102,3 +102,9 @@
       # Go to the new user group's detail page.
       return framework_helpers.FormatAbsoluteURL(
           mr, '/g/%s/' % group_id, include_project=False)
+
+  # def GetGroupCreate(self, **kwargs):
+  #   return self.handler(**kwargs)
+
+  # def PostGroupCreate(self, **kwargs):
+  #   return self.handler(**kwargs)
diff --git a/sitewide/groupdetail.py b/sitewide/groupdetail.py
index b28baa9..4428a53 100644
--- a/sitewide/groupdetail.py
+++ b/sitewide/groupdetail.py
@@ -14,6 +14,7 @@
 import ezt
 
 from framework import exceptions
+from framework import flaskservlet
 from framework import framework_helpers
 from framework import framework_views
 from framework import paginate
@@ -189,6 +190,8 @@
     """
     # 1. Gather data from the request.
     remove_strs = post_data.getall('remove')
+    # TODO(crbug.com/monorail/10936): getall in Flask is getlist
+    # remove_strs = post_data.getlist('remove')
     logging.info('remove_strs = %r', remove_strs)
 
     if not remove_strs:
@@ -208,3 +211,9 @@
       return framework_helpers.FormatAbsoluteURL(
           mr, '/g/%s/' % mr.viewed_username, include_project=False,
           saved=1, ts=int(time.time()))
+
+  # def GetGroupDetail(self, **kwargs):
+  #   return self.handler(**kwargs)
+
+  # def PostGroupDetail(self, **kwargs):
+  #   return self.handler(**kwargs)
diff --git a/sitewide/grouplist.py b/sitewide/grouplist.py
index 3adfaa3..57c46e9 100644
--- a/sitewide/grouplist.py
+++ b/sitewide/grouplist.py
@@ -13,7 +13,7 @@
 
 import ezt
 
-from framework import framework_helpers
+from framework import flaskservlet, framework_helpers
 from framework import permissions
 from framework import servlet
 from framework import urls
@@ -66,6 +66,8 @@
           'User is not permitted to delete groups')
 
     remove_groups = [int(g) for g in post_data.getall('remove')]
+    # TODO(crbug.com/monorail/10936): getall in Flask is getlist
+    # remove_groups = [int(g) for g in post_data.getlist('remove')]
 
     if not mr.errors.AnyErrors():
       self.services.usergroup.DeleteGroups(mr.cnxn, remove_groups)
@@ -76,3 +78,75 @@
       return framework_helpers.FormatAbsoluteURL(
           mr, '/g', include_project=False,
           saved=1, ts=int(time.time()))
+
+  # def GetGroupList(self, **kwargs):
+  #   return self.handler(**kwargs)
+
+  # def PostGroupList(self, **kwargs):
+  #   return self.handler(**kwargs)
+
+
+class GroupDelete(flaskservlet.FlaskServlet):
+  """Shows a page with a simple form to create a user group."""
+
+  _PAGE_TEMPLATE = 'sitewide/group-list-page.ezt'
+
+  def AssertBasePermission(self, mr):
+    """Assert that the user has the permissions needed to view this page."""
+    super(GroupDelete, self).AssertBasePermission(mr)
+
+    if not mr.perms.HasPerm(permissions.VIEW_GROUP, None, None):
+      raise permissions.PermissionException(
+          'User is not allowed to view list of user groups')
+
+  def GatherPageData(self, mr):
+    """Build up a dictionary of data values to use when rendering the page."""
+    group_views = [
+        sitewide_views.GroupView(*groupinfo)
+        for groupinfo in self.services.usergroup.GetAllUserGroupsInfo(mr.cnxn)
+    ]
+    group_views.sort(key=lambda gv: gv.name)
+    offer_group_deletion = mr.perms.CanUsePerm(
+        permissions.DELETE_GROUP, mr.auth.effective_ids, None, [])
+    offer_group_creation = mr.perms.CanUsePerm(
+        permissions.CREATE_GROUP, mr.auth.effective_ids, None, [])
+
+    return {
+        'form_token':
+            xsrf.GenerateToken(mr.auth.user_id, '%s.do' % urls.GROUP_DELETE),
+        'groups':
+            group_views,
+        'offer_group_deletion':
+            ezt.boolean(offer_group_deletion),
+        'offer_group_creation':
+            ezt.boolean(offer_group_creation),
+    }
+
+  def ProcessFormData(self, mr, post_data):
+    """Process the posted form."""
+    if 'removebtn' in post_data:
+      return self.ProcessDeleteGroups(mr, post_data)
+
+  def ProcessDeleteGroups(self, mr, post_data):
+    """Process request to delete groups."""
+    if not mr.perms.CanUsePerm(permissions.DELETE_GROUP, mr.auth.effective_ids,
+                               None, []):
+      raise permissions.PermissionException(
+          'User is not permitted to delete groups')
+
+    remove_groups = [int(g) for g in post_data.getlist('remove')]
+
+    if not mr.errors.AnyErrors():
+      self.services.usergroup.DeleteGroups(mr.cnxn, remove_groups)
+
+    if mr.errors.AnyErrors():
+      self.PleaseCorrect(mr)
+    else:
+      return framework_helpers.FormatAbsoluteURL(
+          mr, '/g', include_project=False, saved=1, ts=int(time.time()))
+
+  def GetGroupDelete(self, **kwargs):
+    return self.handler(**kwargs)
+
+  def PostGroupDelete(self, **kwargs):
+    return self.handler(**kwargs)
diff --git a/sitewide/hostinghome.py b/sitewide/hostinghome.py
index 4a0a47d..b744935 100644
--- a/sitewide/hostinghome.py
+++ b/sitewide/hostinghome.py
@@ -14,16 +14,14 @@
 import settings
 from businesslogic import work_env
 from framework import exceptions
+from framework import flaskservlet
 from framework import permissions
-from framework import servlet
-from framework import template_helpers
 from framework import urls
 from project import project_views
 from sitewide import projectsearch
-from sitewide import sitewide_helpers
 
 
-class HostingHome(servlet.Servlet):
+class HostingHome(flaskservlet.FlaskServlet):
   """HostingHome shows the project list and link to create a project."""
 
   _PAGE_TEMPLATE = 'sitewide/hosting-home-page.ezt'
@@ -105,3 +103,6 @@
     project_url = '/p/%s' % project_name
     self.redirect(project_url, abort=True)
     return 'Redirected to %r' % project_url
+
+  def GetOldHostingHome(self, **kwargs):
+    return self.handler(**kwargs)
diff --git a/sitewide/moved.py b/sitewide/moved.py
index 3f63d24..968422c 100644
--- a/sitewide/moved.py
+++ b/sitewide/moved.py
@@ -13,7 +13,7 @@
 
 import logging
 
-from framework import exceptions
+from framework import exceptions, flaskservlet
 from framework import framework_helpers
 from framework import servlet
 from framework import urls
@@ -60,3 +60,6 @@
         'project_name': mr.specified_project,
         'moved_to_url': moved_to_url,
         }
+
+  # def GetProjectMoved(self, **kwargs):
+  #   return self.handler(**kwargs)
diff --git a/sitewide/projectcreate.py b/sitewide/projectcreate.py
index 83862f6..c3f8cca 100644
--- a/sitewide/projectcreate.py
+++ b/sitewide/projectcreate.py
@@ -15,7 +15,7 @@
 
 import settings
 from businesslogic import work_env
-from framework import exceptions
+from framework import exceptions, flaskservlet
 from framework import filecontent
 from framework import framework_helpers
 from framework import gcs_helpers
@@ -155,3 +155,9 @@
       # Go to the new project's introduction page.
       return framework_helpers.FormatAbsoluteURL(
           mr, urls.ADMIN_INTRO, project_name=project_name)
+
+  # def GetCreateProject(self, **kwargs):
+  #   return self.handler(**kwargs)
+
+  # def PostCreateProject(self, **kwargs):
+  #   return self.handler(**kwargs)
diff --git a/sitewide/test/custom_404_test.py b/sitewide/test/custom_404_test.py
index 71b52f8..b47501d 100644
--- a/sitewide/test/custom_404_test.py
+++ b/sitewide/test/custom_404_test.py
@@ -8,7 +8,7 @@
 from __future__ import division
 from __future__ import absolute_import
 
-import httplib
+from six.moves import http_client
 import unittest
 
 from framework import exceptions
@@ -39,6 +39,4 @@
     _, mr = testing_helpers.GetRequestObjects(path='/p/proj/junk')
 
     page_data = self.servlet.GatherPageData(mr)
-    self.assertEqual(
-      {'http_response_code': httplib.NOT_FOUND},
-      page_data)
+    self.assertEqual({'http_response_code': http_client.NOT_FOUND}, page_data)
diff --git a/sitewide/test/hostinghome_test.py b/sitewide/test/hostinghome_test.py
index f51c9ec..de125a5 100644
--- a/sitewide/test/hostinghome_test.py
+++ b/sitewide/test/hostinghome_test.py
@@ -47,7 +47,7 @@
     self.project_a = self.services.project.TestAddProject('a', project_id=1)
     self.project_b = self.services.project.TestAddProject('b', project_id=2)
 
-    self.servlet = hostinghome.HostingHome('req', 'res', services=self.services)
+    self.servlet = hostinghome.HostingHome(services=self.services)
     self.mr = testing_helpers.MakeMonorailRequest(user_info={'user_id': 111})
 
     self.orig_pipeline_class = projectsearch.ProjectSearchPipeline
diff --git a/sitewide/test/userprofile_test.py b/sitewide/test/userprofile_test.py
index b830fb7..b4e29d8 100644
--- a/sitewide/test/userprofile_test.py
+++ b/sitewide/test/userprofile_test.py
@@ -49,6 +49,7 @@
   mr.viewed_user_auth.user_view = framework_views.UserView(viewed_user_pb)
   mr.viewed_user_name = viewed_user_name
   mr.request = webapp2.Request.blank("/")
+  mr.request_path = mr.request.path
   return mr
 
 
diff --git a/sitewide/userclearbouncing.py b/sitewide/userclearbouncing.py
index 3decdf4..0ae5f4a 100644
--- a/sitewide/userclearbouncing.py
+++ b/sitewide/userclearbouncing.py
@@ -12,6 +12,7 @@
 import time
 
 from framework import framework_helpers
+from framework import flaskservlet
 from framework import permissions
 from framework import servlet
 from framework import timestr
@@ -60,3 +61,9 @@
     return framework_helpers.FormatAbsoluteURL(
         mr, mr.viewed_user_auth.user_view.profile_url, include_project=False,
         saved=1, ts=int(time.time()))
+
+  # def GetUserClearBouncingPage(self, **kwargs):
+  #   return self.handler(**kwargs)
+
+  # def PostUserClearBouncingPage(self, **kwargs):
+  #   return self.handler(**kwargs)
diff --git a/sitewide/userprofile.py b/sitewide/userprofile.py
index bf68c5f..2723e9e 100644
--- a/sitewide/userprofile.py
+++ b/sitewide/userprofile.py
@@ -17,6 +17,7 @@
 import settings
 from businesslogic import work_env
 from framework import framework_helpers
+from framework import flaskservlet
 from framework import framework_views
 from framework import permissions
 from framework import servlet
@@ -146,9 +147,9 @@
     ban_token = None
     ban_spammer_token = None
     if mr.auth.user_id and can_ban:
-      form_token_path = mr.request.path + 'ban.do'
+      form_token_path = mr.request_path + 'ban.do'
       ban_token = xsrf.GenerateToken(mr.auth.user_id, form_token_path)
-      form_token_path = mr.request.path + 'banSpammer.do'
+      form_token_path = mr.request_path + 'banSpammer.do'
       ban_spammer_token = xsrf.GenerateToken(mr.auth.user_id, form_token_path)
 
     can_delete_user = permissions.CanExpungeUsers(mr)
@@ -230,6 +231,12 @@
         mr, mr.viewed_user_auth.user_view.profile_url, include_project=False,
         saved=1, ts=int(time.time()))
 
+  # def GetUserProfilePage(self, **kwargs):
+  #   return self.handler(**kwargs)
+
+  # def PostUserProfilePage(self, **kwargs):
+  #   return self.handler(**kwargs)
+
 
 def _ComputePossibleParentAccounts(
     we, user_view, linked_parent, linked_children):
@@ -245,13 +252,6 @@
   found_emails = [user.email for user in found_users]
   return found_emails
 
-
-class UserProfilePolymer(UserProfile):
-  """New Polymer version of user profiles in Monorail."""
-
-  _PAGE_TEMPLATE = 'sitewide/user-profile-page-polymer.ezt'
-
-
 class BanUser(servlet.Servlet):
   """Bans or un-bans a user."""
 
@@ -269,3 +269,6 @@
     return framework_helpers.FormatAbsoluteURL(
         mr, mr.viewed_user_auth.user_view.profile_url, include_project=False,
         saved=1, ts=int(time.time()))
+
+  # def PostBanUserPage(self, **kwargs):
+  #   return self.handler(**kwargs)
diff --git a/sitewide/usersettings.py b/sitewide/usersettings.py
index bb65ddd..8484afc 100644
--- a/sitewide/usersettings.py
+++ b/sitewide/usersettings.py
@@ -9,7 +9,7 @@
 from __future__ import absolute_import
 
 import time
-import urllib
+from six.moves import urllib
 
 import ezt
 
@@ -63,3 +63,13 @@
         saved=1, ts=int(time.time()))
 
     return url
+
+  # pylint: disable=unused-argument
+  def GetUserSetting(self, **kwargs):
+    return
+    # return self.handler(**kwargs)
+
+  # pylint: disable=unused-argument
+  def PostUserSetting(self, **kwargs):
+    return
+    # return self.handler(**kwargs)
diff --git a/sitewide/userupdates.py b/sitewide/userupdates.py
index ac44c0f..b970614 100644
--- a/sitewide/userupdates.py
+++ b/sitewide/userupdates.py
@@ -22,6 +22,7 @@
 from businesslogic import work_env
 from features import activities
 from framework import servlet
+from framework import flaskservlet
 from framework import urls
 from sitewide import sitewide_helpers
 
@@ -87,6 +88,9 @@
           viewed_user_id=mr.viewed_user_auth.user_id)
     return [project.project_id for project in starred_projects]
 
+  # def GetUserUpdatesProjectsPage(self, **kwargs):
+  #   return self.handler(**kwargs)
+
 
 class UserUpdatesDevelopers(AbstractUserUpdatesPage):
   """Shows a page of updates from developers starred by a user."""
@@ -104,6 +108,9 @@
     logging.debug('StarredUsers: %r', user_ids)
     return user_ids
 
+  # def GetUserUpdatesDevelopersPage(self, **kwargs):
+  #   return self.handler(**kwargs)
+
 
 class UserUpdatesIndividual(AbstractUserUpdatesPage):
   """Shows a page of updates initiated by a user."""
@@ -116,3 +123,6 @@
   def _GetUserIDsForUpdates(self, mr):
     """Returns a list of user IDs whom to retrieve activities from."""
     return [mr.viewed_user_auth.user_id]
+
+  # def GetUserUpdatesPage(self, **kwargs):
+  #   return self.handler(**kwargs)