Merge branch 'daniel/project-list'
diff --git a/app/soc/modules/gsoc/views/dashboard.py b/app/soc/modules/gsoc/views/dashboard.py
index 63db37a..6f8b5e3 100644
--- a/app/soc/modules/gsoc/views/dashboard.py
+++ b/app/soc/modules/gsoc/views/dashboard.py
@@ -340,7 +340,7 @@
   the current user.
   """
 
-  IDX = 0
+  IDX = 5
 
   def __init__(self, data, survey):
     """Initializes the component.
@@ -1026,27 +1026,8 @@
 
   def __init__(self, data):
     """Initializes this component."""
-
-    def getOrganization(entity, *args):
-      """Helper function to get value of organization column."""
-      org_key = GSoCProject.org.get_value_for_datastore(entity)
-      return ndb.Key.from_old_key(org_key).get().name
-
-    list_config = lists.ListConfiguration()
-    list_config.addSimpleColumn('title', 'Title')
-
-    def getStudent(entity, *args):
-      """Helper function to get value of student column."""
-      return ndb.Key.from_old_key(entity.parent_key()).get().public_name
-
-    list_config.addPlainTextColumn('student', 'Student', getStudent)
-    list_config.addPlainTextColumn('org', 'Organization', getOrganization)
-    list_config.setDefaultSort('title')
-    list_config.setRowAction(
-        lambda e, *args: links.LINKER.userId(
-            e.parent_key(), e.key().id(), url_names.GSOC_PROJECT_DETAILS))
-    self._list_config = list_config
-
+    self._list = project_list.getMentoredProjectList(
+        data, data.ndb_profile.key)
     super(ProjectsIMentorComponent, self).__init__(data)
 
   def templatePath(self):
@@ -1060,43 +1041,18 @@
     If the lists as requested is not supported by this component None is
     returned.
     """
-    if lists.getListIndex(self.data.request) != 5:
-      return None
-
-    list_query = project_logic.getAcceptedProjectsQuery(
-        program=self.data.program)
-
-    if self.data.ndb_profile.is_admin:
-      list_query.filter(
-          'org IN',
-          map(lambda key: key.to_old_key(), self.data.ndb_profile.admin_for))
-    else:
-      list_query.filter('mentors', self.data.ndb_profile.key.to_old_key())
-
-    starter = lists.keyStarter
-    # TODO(daniel): enable prefetching from ndb models ('org', 'parent')
-    prefetcher = None
-
-    response_builder = lists.RawQueryContentResponseBuilder(
-        self.data.request, self._list_config, list_query,
-        starter, prefetcher=prefetcher)
-    return response_builder.build()
+    return self._list.getListData()
 
   def context(self):
     """Returns the context of this component."""
     list_configuration_response = lists.ListConfigurationResponse(
-        self.data, self._list_config, idx=5)
-
-    if self.data.ndb_profile.is_admin:
-      title = 'Projects for my orgs'
-    else:
-      title = 'Projects I am a mentor for'
+        self.data, self._list._list_config, idx=0, preload_list=False)
 
     return {
         'name': 'mentoring_projects',
-        'title': title,
+        'title': 'Projects I am a mentor for',
         'lists': [list_configuration_response],
-        'description': ugettext(title),
+        'description': ugettext('Projects I am a mentor for'),
     }
 
 
diff --git a/app/soc/modules/gsoc/views/participants.py b/app/soc/modules/gsoc/views/participants.py
index d3aa9ed..06f5153 100644
--- a/app/soc/modules/gsoc/views/participants.py
+++ b/app/soc/modules/gsoc/views/participants.py
@@ -181,7 +181,9 @@
 
     list_config = lists.ListConfiguration()
     list_config.addPlainTextColumn(
-        'name', 'Name', lambda entity, *args: entity.public_name.strip())
+        'name', 'Public Name', lambda entity, *args: entity.public_name.strip())
+    list_config.addSimpleColumn('first_name', 'First Name')
+    list_config.addSimpleColumn('last_name', 'Last Name')
     list_config.addSimpleColumn('profile_id', 'Username')
     list_config.addPlainTextColumn(
         'email', 'Email', lambda entity, *args: entity.contact.email)
diff --git a/app/summerofcode/logic/project.py b/app/summerofcode/logic/project.py
index 08cdf34..e502b0a 100644
--- a/app/summerofcode/logic/project.py
+++ b/app/summerofcode/logic/project.py
@@ -86,6 +86,20 @@
       project_model.Project.status == project_model.Status.ACCEPTED)
 
 
+def queryAllProjectsForOrgMember(profile_key):
+  """Returns a query to fetch all projects mentored by the specified
+  organization memebr.
+
+  Args:
+    profile_key: ndb.Key of the profile.
+
+  Returns:
+    ndb.Query object to fetch all projects mentored by specified profile.
+  """
+  return project_model.Project.query(
+      project_model.Project.mentors == profile_key)
+
+
 @ndb.transactional
 def updateProject(project_key, project_properties):
   """Updates the specified project based on the specified properties.
diff --git a/app/summerofcode/templates/project_list.py b/app/summerofcode/templates/project_list.py
index f232892..bd7f6f4 100644
--- a/app/summerofcode/templates/project_list.py
+++ b/app/summerofcode/templates/project_list.py
@@ -219,7 +219,7 @@
   list_config.addHtmlColumn('student', 'Student', _getStudent)
   list_config.addHtmlColumn('organization', 'Organization', _getOrganization)
   list_config.addSimpleColumn('status', 'Status')
-  list_config.addHtmlColumn('mentors', 'Mentors', _getMentors)
+  list_config.addPlainTextColumn('mentors', 'Mentors', _getMentors)
   list_config.addHtmlColumn('manage_link', 'Manage', _getManage)
 
   list_config.setRowAction(
@@ -266,3 +266,41 @@
   return ProjectList(
       'summerofcode/_list_component.html', data, list_config,
       query, _PUBLIC_PROJECT_LIST_DESCRIPTION)
+
+
+def getMentoredProjectList(data, profile_key):
+  """Returns an instance of ProjectList class that lists all projects mentored
+  by the specified organization member.
+
+  Args:
+    data: request_data.RequestData for the current request.
+    profile_key: ndb.Key of the profile.
+
+  Returns:
+    The newly created ProjectList object.
+  """
+  list_config = lists.ListConfiguration(add_key_column=False)
+
+  list_config.addPlainTextColumn(
+      'key', 'Key', lambda entity, *args: entity.key.urlsafe(), hidden=True)
+  list_config.addSimpleColumn('title', 'Title')
+
+  # organization column is added only when the user is a mentor for
+  # more than one organization
+  if len(data.ndb_profile.mentor_for) > 1:
+    list_config.addPlainTextColumn(
+        'organization', 'Organization', _getOrganization)
+
+  list_config.addSimpleColumn('status', 'Status')
+  list_config.addPlainTextColumn('mentors', 'Mentors', _getMentors)
+
+  list_config.setRowAction(
+      lambda entity, *args: links.LINKER.userId(
+          entity.key.parent(), entity.key.id(),
+          urls.UrlNames.PROJECT_DETAILS))
+
+  query = project_logic.queryAllProjectsForOrgMember(profile_key)
+
+  return ProjectList(
+      'summerofcode/_list_component.html', data, list_config,
+      query, _PUBLIC_PROJECT_LIST_DESCRIPTION)
diff --git a/tests/app/summerofcode/logic/test_project.py b/tests/app/summerofcode/logic/test_project.py
index 1054d57..42d8075 100644
--- a/tests/app/summerofcode/logic/test_project.py
+++ b/tests/app/summerofcode/logic/test_project.py
@@ -182,6 +182,49 @@
     self.assertSetEqual(set(project_keys), self.accepted_project_keys)
 
 
+class queryAllProjectsForOrgMemberTest(unittest.TestCase):
+  """Unit tests for queryAllProjectsForOrgMember function."""
+
+  def setUp(self):
+    """See unittest.UnitTest.setUp for specification."""
+    program = program_utils.seedGSoCProgram()
+    org = org_utils.seedSOCOrganization(program.key())
+
+    self.mentor = profile_utils.seedNDBProfile(
+        program.key(), mentor_for=[org.key])
+
+    # seed a few accepted, withdrawn and failed projects for the mentor
+    self.accepted_project_keys = set()
+    for _ in range(_TEST_NUMER_OF_PROJECTS):
+      student = profile_utils.seedNDBStudent(program)
+      self.accepted_project_keys.add(
+          project_utils.seedNDBProject(
+              student, program.key(), org_key=org.key,
+              mentor_key=self.mentor.key).key)
+
+      self.accepted_project_keys.add(
+          project_utils.seedNDBProject(
+              student, program.key(), mentor_key=self.mentor.key,
+              status=project_model.Status.WITHDRAWN, org_key=org.key).key)
+
+      self.accepted_project_keys.add(
+          project_utils.seedNDBProject(
+              student, program.key(), mentor_key=self.mentor.key,
+              status=project_model.Status.FAILED, org_key=org.key).key)
+
+    # seed a few accepted projects for other mentors
+    for _ in range(_TEST_NUMER_OF_PROJECTS):
+      student = profile_utils.seedNDBStudent(program)
+      project_utils.seedNDBProject(student, program.key(), org_key=org.key)
+
+  def testsFetchedProjects(self):
+    """Tests that only accepted projects for the organization are fetched."""
+    query = project_logic.queryAllProjectsForOrgMember(self.mentor.key)
+    project_keys = query.fetch(1000, keys_only=True)
+
+    self.assertSetEqual(set(project_keys), self.accepted_project_keys)
+
+
 _OTHER_PROJECT_PROPERTIES = {
     'title': u'Other Title',
     'abstract': u'Other Abstract',