Integrate yesterday's AccessChecker progress and tests upgrade.
diff --git a/app/soc/content/images/gci/logo/banner-gci2013.png b/app/soc/content/images/gci/logo/banner-gci2013.png
new file mode 100644
index 0000000..cd90d1d
--- /dev/null
+++ b/app/soc/content/images/gci/logo/banner-gci2013.png
Binary files differ
diff --git a/app/soc/models/conversation.py b/app/soc/models/conversation.py
new file mode 100644
index 0000000..858a4a8
--- /dev/null
+++ b/app/soc/models/conversation.py
@@ -0,0 +1,122 @@
+# Copyright 2013 the Melange authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Module containing the Conversation model."""
+
+from google.appengine.ext import db
+from google.appengine.ext import ndb
+
+from datetime import datetime
+
+from django.utils import translation
+
+from soc.models import user as user_model
+
+#: Constants for specifiying the type of recipients
+PROGRAM       = 'Program'       #: Types of users within the program
+ORGANIZATION  = 'Organization'  #: Types of users within the specified org
+USER          = 'User'          #: Specific users, specified manually
+
+
+class Conversation(ndb.Model):
+  """Model of a conversation: a thread of messages."""
+
+  #: Subject of the conversation
+  subject = ndb.TextProperty(required=True,
+                             verbose_name=translation.ugettext('Subject'))
+
+  #: User who created the conversation.
+  #: Optional; If None, conversation was created by Melange.
+  creator = ndb.KeyProperty(required=False)
+
+  #: When the conversation was created
+  created_on = ndb.DateTimeProperty(required=True, auto_now_add=True)
+
+  #: When the last message was added
+  last_message_on = ndb.DateTimeProperty(
+      default=datetime.min,
+      verbose_name=translation.ugettext('Last Message'))
+
+  #: What type of recipients
+  recipients_type = ndb.StringProperty(required=True,
+                                       choices=[PROGRAM, ORGANIZATION, USER])
+
+  #: Program under which the conversation exists.
+  #: If recipient type is 'Program', this is also the scope of recipients.
+  program = ndb.KeyProperty(required=True,
+                            verbose_name=translation.ugettext('Program'))
+
+  #: Organization to limit to if recipients type is organization.
+  #: Ignored if recipient type is not 'Organization'.
+  organization = ndb.KeyProperty(
+      required=False,
+      verbose_name=translation.ugettext('Organization'))
+
+  #: Include admins as recipients if recipients type is program,
+  #: or organization admins as recipients if recipients type is organization.
+  #: Ignored if recipient type is not 'Program' or 'Organization'.
+  include_admins = ndb.BooleanProperty(required=False)
+
+  #: Include mentors as recipients if recipients type is program,
+  #: or organization mentors as recipients if recipients type is organization.
+  #: Ignored if recipient type is not 'Program' or 'Organization'.
+  include_mentors = ndb.BooleanProperty(required=False)
+
+  #: Include students as recipients if recipients type is program.
+  #: Ignored if recipient type is not 'Program'.
+  include_students = ndb.BooleanProperty(required=False)
+
+  #: Whether users will be automatically added/removed if they start/stop
+  #: matching the criteria defined above. Ignored if recipients type is 'User'.
+  auto_update_users = ndb.BooleanProperty(
+      default=True,
+      verbose_name=translation.ugettext('Automatically Update Users'))
+  auto_update_users.help_text = translation.ugettext(
+      'If set, users will be automatically added and removed from the '
+      'conversation if they no longer fit the criteria.')
+
+
+class ConversationUser(ndb.Model):
+  """Model representing a user's involvement in a conversation.
+
+  An instance of this model is created for every user in every conversation.
+
+  In addition to being a record of a user's involvement in a conversation, it
+  stores the time of the last message seen by the user, and it also store's the
+  user's preferences for this conversation.
+  """
+
+  #: Conversation the preferences apply to
+  conversation = ndb.KeyProperty(kind=Conversation, required=True)
+
+  #: User the preferences are for
+  user = ndb.KeyProperty(required=True)
+
+  #: Conversation's Program, to aid with querying
+  program = ndb.ComputedProperty(lambda self: self.conversation.get().program)
+
+  #: Conversation's last_message_on, to aid with querying
+  last_message_on = ndb.ComputedProperty(
+      lambda self: self.conversation.get().last_message_on)
+
+  #: Time of the last message seen by this user in this conversation
+  last_message_seen_on = ndb.DateTimeProperty(default=datetime.min)
+
+  #: Preference for receiving email notifications for new messages
+  enable_notifications = ndb.BooleanProperty(
+      default=True,
+      verbose_name=translation.ugettext('Get Email Notifications'))
+  enable_notifications.help_test = translation.ugettext(
+      'If set, you will receive email notifications about new '
+      'messages in this conversation.')
diff --git a/app/soc/models/message.py b/app/soc/models/message.py
new file mode 100644
index 0000000..d13e269
--- /dev/null
+++ b/app/soc/models/message.py
@@ -0,0 +1,45 @@
+# Copyright 2013 the Melange authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Module containing the Message and MessageSeen models."""
+
+from google.appengine.ext import ndb
+
+from django.utils import translation
+
+from soc.models import conversation as conversation_model
+
+
+class Message(ndb.Model):
+  """Model of a message within a conversation.
+
+  Parent:
+    soc.models.conversation.Conversation
+  """
+
+  #: Conversation the message belongs to
+  conversation = ndb.KeyProperty(kind=conversation_model.Conversation,
+                                 required=True)
+
+  #: User who wrote the message. If None, message was authored by Melange.
+  author = ndb.KeyProperty(required=False,
+                           verbose_name=translation.ugettext('Author'))
+
+  #: Content of the message
+  content = ndb.TextProperty(required=True,
+                             verbose_name=translation.ugettext('Message'))
+
+  #: Time when the message was sent
+  sent_on = ndb.DateTimeProperty(required=True, auto_now_add=True,
+                                 verbose_name=translation.ugettext('Time Sent'))
diff --git a/tests/app/soc/modules/gci/views/test_homepage.py b/tests/app/soc/modules/gci/views/test_homepage.py
index 90668a3..976a583 100644
--- a/tests/app/soc/modules/gci/views/test_homepage.py
+++ b/tests/app/soc/modules/gci/views/test_homepage.py
@@ -104,4 +104,4 @@
     # TOOD
     #apply_tmpl = response.context['apply']
     #self.assertTrue(apply_tmpl.data.profile)
-    #self.assertFalse('profile_link' in apply_tmpl.context())
+    #self.assertNotIn('profile_link', apply_tmpl.context())
diff --git a/tests/app/soc/modules/gci/views/test_task.py b/tests/app/soc/modules/gci/views/test_task.py
index 39117c9..13f026d 100644
--- a/tests/app/soc/modules/gci/views/test_task.py
+++ b/tests/app/soc/modules/gci/views/test_task.py
@@ -500,7 +500,7 @@
     self.profile_helper.createMentor(self.org)
 
     profile = self.profile_helper.profile
-    self.assertFalse(profile.key() in self.task.subscribers)
+    self.assertNotIn(profile.key(), self.task.subscribers)
 
     url = self._taskPageUrl(self.task)
     response = self.buttonPost(url, 'button_subscribe')
@@ -524,7 +524,7 @@
 
     task = task_model.GCITask.get(self.task.key())
     self.assertResponseRedirect(response)
-    self.assertFalse(profile.key() in task.subscribers)
+    self.assertNotIn(profile.key(), task.subscribers)
 
   def testPostSubmitWork(self):
     """Tests for submitting work.
diff --git a/tests/app/soc/modules/gsoc/views/test_homepage.py b/tests/app/soc/modules/gsoc/views/test_homepage.py
index d9511dd..9749dd5 100644
--- a/tests/app/soc/modules/gsoc/views/test_homepage.py
+++ b/tests/app/soc/modules/gsoc/views/test_homepage.py
@@ -122,4 +122,4 @@
     self.assertHomepageTemplatesUsed(response)
     apply_tmpl = response.context['apply']
     self.assertTrue(apply_tmpl.data.profile)
-    self.assertFalse('profile_link' in apply_tmpl.context())
+    self.assertNotIn('profile_link', apply_tmpl.context())
diff --git a/tests/app/soc/modules/gsoc/views/test_org_home.py b/tests/app/soc/modules/gsoc/views/test_org_home.py
index 8c1126a..89b0834 100644
--- a/tests/app/soc/modules/gsoc/views/test_org_home.py
+++ b/tests/app/soc/modules/gsoc/views/test_org_home.py
@@ -105,17 +105,17 @@
     return response.context
 
   def assertNoStudent(self, context):
-    self.assertFalse('student_apply_block' in context)
-    self.assertFalse('student_profile_link' in context)
-    self.assertFalse('submit_proposal_link' in context)
+    self.assertNotIn('student_apply_block', context)
+    self.assertNotIn('student_profile_link', context)
+    self.assertNotIn('submit_proposal_link', context)
 
   def assertNoMentor(self, context):
-    self.assertFalse('mentor_apply_block' in context)
-    self.assertFalse('mentor_profile_link' in context)
-    self.assertFalse('role' in context)
-    self.assertFalse('mentor_applied' in context)
-    self.assertFalse('invited_role' in context)
-    self.assertFalse('mentor_request_link' in context)
+    self.assertNotIn('mentor_apply_block', context)
+    self.assertNotIn('mentor_profile_link', context)
+    self.assertNotIn('role', context)
+    self.assertNotIn('mentor_applied', context)
+    self.assertNotIn('invited_role', context)
+    self.assertNotIn('mentor_request_link', context)
 
   def assertMentor(self):
     self.profile_helper.createMentor(self.org)
@@ -123,11 +123,11 @@
     self.assertNoStudent(context)
 
     self.assertIn('mentor_apply_block', context)
-    self.assertFalse('mentor_profile_link' in context)
+    self.assertNotIn('mentor_profile_link', context)
     self.assertEqual('a mentor', context['role'])
-    self.assertFalse('mentor_applied' in context)
-    self.assertFalse('invited_role' in context)
-    self.assertFalse('mentor_request_link' in context)
+    self.assertNotIn('mentor_applied', context)
+    self.assertNotIn('invited_role', context)
+    self.assertNotIn('mentor_request_link', context)
 
   def testAnonymousPreSignup(self):
     self.timeline_helper.orgSignup()
@@ -136,24 +136,24 @@
 
     self.assertIn('mentor_apply_block', context)
     self.assertIn('mentor_profile_link', context)
-    self.assertFalse('role' in context)
-    self.assertFalse('mentor_applied' in context)
-    self.assertFalse('invited_role' in context)
-    self.assertFalse('mentor_request_link' in context)
+    self.assertNotIn('role', context)
+    self.assertNotIn('mentor_applied', context)
+    self.assertNotIn('invited_role', context)
+    self.assertNotIn('mentor_request_link', context)
 
   def testAnonymousDuringSignup(self):
     self.timeline_helper.studentSignup()
     context = self.homepageContext()
     self.assertIn('student_apply_block', context)
     self.assertIn('student_profile_link', context)
-    self.assertFalse('submit_proposal_link' in context)
+    self.assertNotIn('submit_proposal_link', context)
 
-    self.assertFalse('mentor_apply_block' in context)
+    self.assertNotIn('mentor_apply_block', context)
     self.assertIn('mentor_profile_link', context)
-    self.assertFalse('role' in context)
-    self.assertFalse('mentor_applied' in context)
-    self.assertFalse('invited_role' in context)
-    self.assertFalse('mentor_request_link' in context)
+    self.assertNotIn('role', context)
+    self.assertNotIn('mentor_applied', context)
+    self.assertNotIn('invited_role', context)
+    self.assertNotIn('mentor_request_link', context)
 
   def testAnonymousPostSignup(self):
     self.timeline_helper.postStudentSignup()
@@ -162,22 +162,22 @@
 
     self.assertIn('mentor_apply_block', context)
     self.assertIn('mentor_profile_link', context)
-    self.assertFalse('role' in context)
-    self.assertFalse('mentor_applied' in context)
-    self.assertFalse('invited_role' in context)
-    self.assertFalse('mentor_request_link' in context)
+    self.assertNotIn('role', context)
+    self.assertNotIn('mentor_applied', context)
+    self.assertNotIn('invited_role', context)
+    self.assertNotIn('mentor_request_link', context)
 
   def testAnonymousStudentsAnnounced(self):
     self.timeline_helper.studentsAnnounced()
     context = self.homepageContext()
     self.assertNoStudent(context)
 
-    self.assertFalse('mentor_apply_block' in context)
-    self.assertFalse('mentor_profile_link' in context)
-    self.assertFalse('role' in context)
-    self.assertFalse('mentor_applied' in context)
-    self.assertFalse('invited_role' in context)
-    self.assertFalse('mentor_request_link' in context)
+    self.assertNotIn('mentor_apply_block', context)
+    self.assertNotIn('mentor_profile_link', context)
+    self.assertNotIn('role', context)
+    self.assertNotIn('mentor_applied', context)
+    self.assertNotIn('invited_role', context)
+    self.assertNotIn('mentor_request_link', context)
 
   def testMentorPreSignup(self):
     self.timeline_helper.orgSignup()
@@ -201,11 +201,11 @@
     self.assertNoStudent(context)
 
     self.assertIn('mentor_apply_block', context)
-    self.assertFalse('mentor_profile_link' in context)
+    self.assertNotIn('mentor_profile_link', context)
     self.assertEqual('an administrator', context['role'])
-    self.assertFalse('mentor_applied' in context)
-    self.assertFalse('invited_role' in context)
-    self.assertFalse('mentor_request_link' in context)
+    self.assertNotIn('mentor_applied', context)
+    self.assertNotIn('invited_role', context)
+    self.assertNotIn('mentor_request_link', context)
 
   def testAppliedMentor(self):
     self.profile_helper.createMentorRequest(self.org)
@@ -213,11 +213,11 @@
     self.assertNoStudent(context)
 
     self.assertIn('mentor_apply_block', context)
-    self.assertFalse('mentor_profile_link' in context)
-    self.assertFalse('role' in context)
+    self.assertNotIn('mentor_profile_link', context)
+    self.assertNotIn('role', context)
     self.assertIn('mentor_applied', context)
-    self.assertFalse('invited_role' in context)
-    self.assertFalse('mentor_request_link' in context)
+    self.assertNotIn('invited_role', context)
+    self.assertNotIn('mentor_request_link', context)
 
   def testInvitedMentor(self):
     self.profile_helper.createInvitation(self.org, 'mentor')
@@ -225,11 +225,11 @@
     self.assertNoStudent(context)
 
     self.assertIn('mentor_apply_block', context)
-    self.assertFalse('mentor_profile_link' in context)
-    self.assertFalse('role' in context)
-    self.assertFalse('mentor_applied' in context)
+    self.assertNotIn('mentor_profile_link', context)
+    self.assertNotIn('role', context)
+    self.assertNotIn('mentor_applied', context)
     self.assertEqual('a mentor', context['invited_role'])
-    self.assertFalse('mentor_request_link' in context)
+    self.assertNotIn('mentor_request_link', context)
 
   def testInvitedOrgAdmin(self):
     self.profile_helper.createInvitation(self.org, 'org_admin')
@@ -237,18 +237,18 @@
     self.assertNoStudent(context)
 
     self.assertIn('mentor_apply_block', context)
-    self.assertFalse('mentor_profile_link' in context)
-    self.assertFalse('role' in context)
-    self.assertFalse('mentor_applied' in context)
+    self.assertNotIn('mentor_profile_link', context)
+    self.assertNotIn('role', context)
+    self.assertNotIn('mentor_applied', context)
     self.assertEqual('an administrator', context['invited_role'])
-    self.assertFalse('mentor_request_link' in context)
+    self.assertNotIn('mentor_request_link', context)
 
   def testStudentDuringSignup(self):
     self.timeline_helper.studentSignup()
     self.profile_helper.createStudent()
     context = self.homepageContext()
     self.assertIn('student_apply_block', context)
-    self.assertFalse('student_profile_link' in context)
+    self.assertNotIn('student_profile_link', context)
     self.assertIn('submit_proposal_link', context)
     self.assertNoMentor(context)
 
diff --git a/tests/app/soc/modules/gsoc/views/test_org_profile.py b/tests/app/soc/modules/gsoc/views/test_org_profile.py
index 9f9de11..83b317b 100644
--- a/tests/app/soc/modules/gsoc/views/test_org_profile.py
+++ b/tests/app/soc/modules/gsoc/views/test_org_profile.py
@@ -229,7 +229,7 @@
     self.assertEqual(context['page_name'], 'Organization profile')
     self.assertIn('org_home_page_link', context)
     self.assertIn('page_name', context)
-    self.assertFalse('slot_transfer_page_link' in context)
+    self.assertNotIn('slot_transfer_page_link', context)
 
     self.gsoc.allocations_visible = True
     self.gsoc.put()
@@ -242,7 +242,7 @@
     response = self.get(url)
     self.assertResponseOK(response)
     self.assertOrgProfilePageTemplatesUsed(response)
-    self.assertFalse('slot_transfer_page_link' in response.context)
+    self.assertNotIn('slot_transfer_page_link', response.context)
 
   def test404IsReturnedWhenOrgDoesNotExists(self):
     """Tests that when an org admin tries to access the profile page for an
diff --git a/tests/app/soc/modules/gsoc/views/test_profile_show.py b/tests/app/soc/modules/gsoc/views/test_profile_show.py
index 4941f5e..06b195d 100644
--- a/tests/app/soc/modules/gsoc/views/test_profile_show.py
+++ b/tests/app/soc/modules/gsoc/views/test_profile_show.py
@@ -77,8 +77,8 @@
     self.assertIn('program_name', context)
     self.assertIn('profile', context)
     self.assertIn('css_prefix', context)
-    self.assertFalse('submit_tax_link' in context)
-    self.assertFalse('submit_enrollment_link' in context)
+    self.assertNotIn('submit_tax_link', context)
+    self.assertNotIn('submit_enrollment_link', context)
 
     expected_page_name = '%s Profile - %s' % (
         self.profile_helper.program.short_name,
diff --git a/tests/app/soc/modules/gsoc/views/test_proposal_review.py b/tests/app/soc/modules/gsoc/views/test_proposal_review.py
index 1f326ca..6d000b7 100644
--- a/tests/app/soc/modules/gsoc/views/test_proposal_review.py
+++ b/tests/app/soc/modules/gsoc/views/test_proposal_review.py
@@ -274,7 +274,7 @@
     response = self.get(url)
 
     proposal = GSoCProposal.get(proposal.key())
-    self.assertFalse(other_mentor.profile.key() in proposal.possible_mentors)
+    self.assertNotIn(other_mentor.profile.key(), proposal.possible_mentors)
 
   def testPubliclyVisibleButton(self):
     self.profile_helper.createStudent()