Merge branch 'bye-bye-linkable'
diff --git a/app/soc/logic/helper/notifications.py b/app/soc/logic/helper/notifications.py
index 5e59fcc..cbd703a 100644
--- a/app/soc/logic/helper/notifications.py
+++ b/app/soc/logic/helper/notifications.py
@@ -31,6 +31,12 @@
 DEF_NEW_REQUEST = ugettext(
     '[%(org)s] New request from %(requester)s to become a %(role_verbose)s')
 
+DEF_NEW_CONNECTION = ugettext(
+  'New connection to %(org)s' )
+
+DEF_NEW_ANONYMOUS_CONNECTION = ugettext(
+  'New Google Summer of Code Connection')
+
 DEF_ACCEPTED_ORG = ugettext(
     '[%(org)s] Your organization application has been accepted.')
 
@@ -48,9 +54,22 @@
 DEF_ORG_INVITE_NOTIFICATION_TEMPLATE = \
     'soc/notification/invitation.html'
 
+#TODO(dcrodman): This needs to be removed once connection is stable.
 DEF_NEW_REQUEST_NOTIFICATION_TEMPLATE = \
     'soc/notification/new_request.html'
 
+DEF_NEW_CONNECTION_NOTIFICATION_TEMPLATE = \
+    'v2/modules/gsoc/notification/initiated_connection.html'
+
+DEF_NEW_ANONYMOUS_CONNECTION_NOTIFICATION_TEMPLATE = \
+    'v2/modules/gsoc/notification/anonymous_connection.html'
+
+DEF_ACCEPTED_ORG_TEMPLATE = \
+    'soc/notification/org_accepted.html'
+
+DEF_REJECTED_ORG_TEMPLATE = \
+    'soc/notification/org_rejected.html'
+
 DEF_HANDLED_REQUEST_NOTIFICATION_TEMPLATE = \
     'soc/notification/handled_request.html'
 
@@ -60,12 +79,76 @@
 DEF_MENTOR_WELCOME_MAIL_TEMPLATE = \
     'soc/notification/mentor_welcome_mail.html'
 
+def connectionContext(data, connection, receivers, message, is_user=False):
+  """Sends out a notification email to all individuals involved in the newly 
+  created connection.
 
+  Args: 
+    data: RequestData object with organization and user set
+    connection: An instance of GSoCConnection.
+    receivers: The email(s) of the org or user who is will be "receiving"
+        the connection. should be the opposite of sender.
+    message: The contents of the message field from the connection form.
+    is_user: True if a user is the one who initiated the connection.
+  Returns:
+    A dictionary containing a context for the mail message to be sent to
+    the receiver(s) regarding a new connection.
+  """
+
+  subject = DEF_NEW_CONNECTION % {'org' : connection.organization.name}
+  request_url = data.redirect.show_connection(connection.parent(), 
+      connection).url(full=True)
+ 
+  message_properties = {
+      'org' : connection.organization.name, 
+      'request_url' : request_url,
+      'is_user' : is_user,
+      'message' : message
+  }
+  template = DEF_NEW_CONNECTION_NOTIFICATION_TEMPLATE
+  return getContext(data, receivers, message_properties, subject, template)
+
+def anonymousConnectionContext(data, email, role, hash, message):
+  """Sends out a notification email to users who have neither user nor 
+  profile entities alerting them that an org admin has attempted to 
+  initiate a connection with them. 
+
+  Args:
+    data: A RequestData object for the connection views.
+    email: Email address of the user meeting the above criteria.
+    role: A string role ('mentor' or 'org_admin') to grant the
+        user when they register.
+    message: The contents of the message field from the connection form.
+  Returns:
+    A dictionary containing a context for the mail message to be sent to
+    the receiver(s) regarding a new anonymous connection.
+  """
+
+  assert isSet(data.profile)
+  assert isSet(data.organization)
+
+  url = data.redirect.profile_anonymous_connection(url_role, 
+      hash).url(full=True)
+
+  message_properties = {
+      'requester' : data.profile.link_id,
+      'org' : data.organization.name,
+      'role' : role,
+      'url' : url,
+      'message' : message
+  }
+
+  subject = DEF_NEW_ANONYMOUS_CONNECTION
+  template = DEF_NEW_ANONYMOUS_CONNECTION_NOTIFICATION_TEMPLATE
+
+  return getContext(data, email, message_properties, subject, template)
+
+#TODO(dcrodman): This needs to be removed once connection is stable.
 def inviteContext(data, invite):
   """Sends out an invite notification to the user the request is for.
 
   Args:
-    data: a RequestData object with 'invite' and 'invite_profile' set
+    data: a RequestData object with 'invite' and 'invite_profile' set.
   """
 
   assert isSet(data.invite_profile)
@@ -94,11 +177,12 @@
   return getContext(data, [to_email], message_properties, subject, template)
 
 
+#TODO(dcrodman): This needs to be removed once connection is stable.
 def requestContext(data, request, admin_emails):
   """Sends out a notification to the persons who can process this Request.
 
   Args:
-    request_entity: an instance of Request model
+    request_entity: an instance of Request model.
   """
 
   assert isSet(data.organization)
@@ -121,11 +205,12 @@
   return getContext(data, admin_emails, message_properties, subject, template)
 
 
+#TODO(dcrodman): This needs to be removed once connection is stable.
 def handledRequestContext(data, status):
   """Sends a message that the request to get a role has been handled.
 
   Args:
-    data: a RequestData object
+    data: a RequestData object.
   """
   assert isSet(data.request_entity)
   assert isSet(data.requester_profile)
@@ -153,11 +238,12 @@
   return getContext(data, [to_email], message_properties, subject, template)
 
 
+#TODO(dcrodman): This needs to be removed once connection is stable.
 def handledInviteContext(data):
   """Sends a message that the invite to obtain a role has been handled.
 
   Args:
-    data: a RequestData object
+    data: a RequestData object.
   """
 
   assert isSet(data.invite)
@@ -193,7 +279,7 @@
 
   Args:
     profile: Profile of the user to who will receive the welcome email.
-    data: RequestData object
+    data: RequestData object.
     messages: ProgramMessages instance containing the message to be sent.
 
   Returns:
@@ -250,8 +336,11 @@
 
 
 def getDefaultContext(request_data, emails, subject, extra_context=None):
-  """Returns a dictionary with the default context for the emails that
-  are sent in this module.
+  """Generate a dictionary with a default context.
+
+  Returns:
+    A dictionary with the default context for the emails that are sent 
+    in this module.
   """
   default_context  = {}
   default_context['sender_name'] = 'The %s Team' % (
@@ -278,10 +367,13 @@
   """Sends out a notification to the specified user.
 
   Args:
-    receivers: email addresses to which the notification should be sent
-    message_properties : message properties
-    subject : subject of notification email
-    template : template used for generating notification
+    receivers: Email addresses to which the notification should be sent.
+    message_properties : Message properties.
+    subject : Subject of notification email.
+    template : Template used for generating notification.
+  Returns:
+    A dictionary containing the context for a message to be sent to one
+    or more recipients.
   """
   message_properties['sender_name'] = 'The %s Team' % (data.site.site_name)
   message_properties['program_name'] = data.program.name
diff --git a/app/soc/modules/gci/views/document.py b/app/soc/modules/gci/views/document.py
index e2d38a9..a923d1a 100644
--- a/app/soc/modules/gci/views/document.py
+++ b/app/soc/modules/gci/views/document.py
@@ -30,7 +30,7 @@
 class GCIDocumentForm(forms.GCIModelForm, document.DocumentForm):
   """Django form for creating documents."""
 
-  dashboard_visibility = forms.MultipleChoiceField(
+  dashboard_visibility = forms.MultipleChoiceField(required=False,
       choices=[(v, v) for v in document_model.Document.VISIBILITY],
       widget=forms.CheckboxSelectMultiple)
 
diff --git a/app/soc/modules/gsoc/views/helper/url_patterns.py b/app/soc/modules/gsoc/views/helper/url_patterns.py
index 31b85d5..a00769c 100644
--- a/app/soc/modules/gsoc/views/helper/url_patterns.py
+++ b/app/soc/modules/gsoc/views/helper/url_patterns.py
@@ -35,6 +35,7 @@
 PROPOSAL  = namedIdBasedPattern(['sponsor', 'program'])
 REVIEW    = namedIdBasedPattern(['sponsor', 'program', 'user'])
 PROJECT   = namedIdBasedPattern(['sponsor', 'program', 'user'])
+COMMENT   = namedIdBasedPattern(['sponsor', 'program', 'user'])
 SURVEY_RECORD = namedIdBasedPattern(['sponsor', 'program', 'survey', 'user'])
 GRADING_RECORD = '/'.join([PROJECT, r'(?P<group>(\d+))', r'(?P<record>(\d+))'])
 PREFIXES = "(gsoc_program|gsoc_org)"
diff --git a/app/soc/views/document.py b/app/soc/views/document.py
index 0b03b84..24b6828 100644
--- a/app/soc/views/document.py
+++ b/app/soc/views/document.py
@@ -25,7 +25,7 @@
 class DocumentForm(forms.ModelForm):
   """Django form for creating documents."""
 
-  dashboard_visibility = forms.MultipleChoiceField(
+  dashboard_visibility = forms.MultipleChoiceField(required=False,
       choices=[(v, v) for v in document_model.Document.VISIBILITY],
       widget=forms.CheckboxSelectMultiple)
 
diff --git a/app/soc/views/helper/url_patterns.py b/app/soc/views/helper/url_patterns.py
index c92e1da..25f22cf 100644
--- a/app/soc/views/helper/url_patterns.py
+++ b/app/soc/views/helper/url_patterns.py
@@ -12,8 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-"""Module for constructing core URL patterns
-"""
+"""Module for constructing core URL patterns."""
 
 
 from django.conf.urls.defaults import url as django_url
@@ -87,13 +86,16 @@
 PROGRAM   = namedLinkIdPattern(['sponsor', 'program'])
 CREATE_PROFILE = _role + namedLinkIdPattern(['sponsor', 'program'])
 PROFILE   = namedLinkIdPattern(['sponsor', 'program', 'user'])
+MESSAGE = namedIdBasedPattern(['sponsor', 'program', 'user'])
 DOCUMENT_FMT      = _document
 ORG_DOCUMENT_FMT  = _org_document
 ORG       = namedLinkIdPattern(['sponsor', 'program', 'organization'])
 INVITE    = _mentor_role + ORG
 REQUEST   = _mentor_role + ORG
-USER_ID	  = namedIdBasedPattern(['sponsor', 'program', 'user'])
-USER_ORG  = namedLinkIdPattern(['sponsor', 'program', 'user', 'organization'])
-
 
 USER = namedLinkIdPattern(['link_id'])
+USER_ID	  = namedIdBasedPattern(['sponsor', 'program', 'user'])
+USER_ORG  = namedLinkIdPattern(['sponsor', 'program', 'user', 'organization'])
+CONNECT = namedLinkIdPattern(['sponsor', 'program', 'organization', 'link_id'])
+CONNECTION = namedIdBasedPattern(['sponsor', 'program', 'user'])
+ANONYMOUS_CONNECTION = _role + namedKeyBasedPattern(['sponsor', 'program'])
diff --git a/tests/app/soc/modules/gci/views/test_document.py b/tests/app/soc/modules/gci/views/test_document.py
index 80a5382..6e7cba9 100644
--- a/tests/app/soc/modules/gci/views/test_document.py
+++ b/tests/app/soc/modules/gci/views/test_document.py
@@ -73,7 +73,7 @@
     # TODO(SRabbelier): test document ACL
     pass
 
-  def testCreateDocument(self):
+  def testCreateDocumentWithDashboardVisibility(self):
     self.data.createHost()
     url = '/gci/document/edit/gci_program/%s/doc' % self.gci.key().name()
     response = self.get(url)
@@ -96,3 +96,27 @@
     key_name = properties['key_name']
     document = Document.get_by_key_name(key_name)
     self.assertPropertiesEqual(properties, document)
+
+  def testCreateDocumentWithDashboardVisibility(self):
+    self.data.createHost()
+    url = '/gci/document/edit/gci_program/%s/doc' % self.gci.key().name()
+    response = self.get(url)
+    self.assertGCITemplatesUsed(response)
+    self.assertTemplateUsed(response, 'v2/modules/gci/document/base.html')
+    self.assertTemplateUsed(response, 'v2/modules/gci/_form.html')
+
+    # test POST
+    override = {
+        'prefix': 'gci_program', 'scope': self.gci, 'link_id': 'doc',
+        'key_name': DocumentKeyNameProvider(), 'modified_by': self.data.user,
+        'home_for': None, 'author': self.data.user, 'is_featured': None,
+        'write_access': 'admin', 'read_access': 'public',
+        'dashboard_visibility': [],
+    }
+    properties = seeder_logic.seed_properties(Document, properties=override)
+    response = self.post(url, properties)
+    self.assertResponseRedirect(response, url)
+
+    key_name = properties['key_name']
+    document = Document.get_by_key_name(key_name)
+    self.assertPropertiesEqual(properties, document)
diff --git a/tests/app/soc/modules/gsoc/views/test_document.py b/tests/app/soc/modules/gsoc/views/test_document.py
index 09086aa..e8fd412 100644
--- a/tests/app/soc/modules/gsoc/views/test_document.py
+++ b/tests/app/soc/modules/gsoc/views/test_document.py
@@ -58,7 +58,7 @@
     # TODO(SRabbelier): test document ACL
     pass
 
-  def testCreateDocument(self):
+  def testCreateDocumentWithDashboardVisibility(self):
     self.data.createHost()
     url = '/gsoc/document/edit/gsoc_program/%s/doc' % self.gsoc.key().name()
     response = self.get(url)
@@ -82,6 +82,30 @@
     document = Document.get_by_key_name(key_name)
     self.assertPropertiesEqual(properties, document)
 
+  def testCreateDocumentNoDashboardVisibility(self):
+    self.data.createHost()
+    url = '/gsoc/document/edit/gsoc_program/%s/doc' % self.gsoc.key().name()
+    response = self.get(url)
+    self.assertGSoCTemplatesUsed(response)
+    self.assertTemplateUsed(response, 'v2/modules/gsoc/document/base.html')
+    self.assertTemplateUsed(response, 'v2/modules/gsoc/_form.html')
+
+    # test POST
+    override = {
+        'prefix': 'gsoc_program', 'scope': self.gsoc, 'link_id': 'doc',
+        'key_name': DocumentKeyNameProvider(), 'modified_by': self.data.user,
+        'home_for': None, 'author': self.data.user, 'is_featured': None,
+        'write_access': 'admin', 'read_access': 'public',
+        'dashboard_visibility': [],
+    }
+    properties = seeder_logic.seed_properties(Document, properties=override)
+    response = self.post(url, properties)
+    self.assertResponseRedirect(response, url)
+
+    key_name = properties['key_name']
+    document = Document.get_by_key_name(key_name)
+    self.assertPropertiesEqual(properties, document)
+
 
 # TODO(nathaniel): More than just a simple smoke test.
 class EventsPageTest(GSoCDjangoTestCase):