Dropdown for how did you hear about program on student registration.

In student registration form, How did you hear about the program?
is converted from text area to dropdown containing specific
questions and also there is an option to student to enter
the answer in a text field.

BUG=https://code.google.com/p/soc/issues/detail?id=2224

Change-Id: I945aa18fb6a6d0d51ab666c2f89323c2ade7f678
diff --git a/app/codein/views/profile.py b/app/codein/views/profile.py
index b39b245..85cd7c4 100644
--- a/app/codein/views/profile.py
+++ b/app/codein/views/profile.py
@@ -336,7 +336,15 @@
     fields[profile_view.TEE_SIZE_LABEL] = profile.tee_size
     fields[profile_view.GENDER_LABEL] = (
         profile_view._GENDER_ENUM_TO_VERBOSE_MAP[profile.gender])
-    fields[profile_view.PROGRAM_KNOWLEDGE_LABEL] = profile.program_knowledge
+
+    if profile.program_knowledge not in (
+      [choice_id for choice_id, _ in profile_view.PROGRAM_KNOWLEDGE_CHOICES]):
+      fields[profile_view.PROGRAM_KNOWLEDGE_LABEL] = profile.program_knowledge
+    else:
+      fields[profile_view.PROGRAM_KNOWLEDGE_LABEL] = (
+        profile_view._PROGRAM_KNOWLEDGE_ENUM_TO_VERBOSE_MAP[(
+          profile.program_knowledge)])
+
     groups.append(
         readonly.Group(
             data, 'codein/readonly/_group.html',
diff --git a/app/melange/content/js/templates/profile/profile_form.js b/app/melange/content/js/templates/profile/profile_form.js
index 0d5eee3..b0326eb 100644
--- a/app/melange/content/js/templates/profile/profile_form.js
+++ b/app/melange/content/js/templates/profile/profile_form.js
@@ -43,5 +43,24 @@
         jQuery.uniform.update();
       }
     });
+
+    var otherKnowledge = jQuery("#melange-program_knowledge_other-textarea");
+    var knowledge = jQuery("#program_knowledge");
+
+    var showOtherKnowledge = (knowledge.val() == 'other');
+
+    otherKnowledge.toggle(showOtherKnowledge);
+
+    // Other program knowledge fields should be visible
+    // only when the dropdown value is other
+    knowledge.on('change', function () {
+      if(jQuery(this).val() == "other"){
+          otherKnowledge.show();
+      } else {
+          otherKnowledge.hide();
+          otherKnowledge.find(":input").val("");
+          jQuery.uniform.update();
+      }
+    });
   }
 );
diff --git a/app/melange/views/profile.py b/app/melange/views/profile.py
index 9065855..0b2a72a 100644
--- a/app/melange/views/profile.py
+++ b/app/melange/views/profile.py
@@ -361,6 +361,33 @@
     (_GENDER_OTHER_ID, _GENDER_OTHER_VERBOSE),
     (_GENDER_NOT_DISCLOSED_ID, _GENDER_NOT_DISCLOSED_VERBOSE))
 
+_PROGRAM_KNOWLEDGE_SCHOOL_ID = 'school'
+_PROGRAM_KNOWLEDGE_SITE_ID = 'site'
+_PROGRAM_KNOWLEDGE_FRIEND_ID = 'friend'
+_PROGRAM_KNOWLEDGE_INTERNET_ID = 'internet'
+_PROGRAM_KNOWLEDGE_OTHER_ID = 'other'
+
+_PROGRAM_KNOWLEDGE_DEFAULT_VERBOSE = translation.ugettext('Please select')
+_PROGRAM_KNOWLEDGE_SCHOOL_VERBOSE = translation.ugettext(
+    'At school/from my professor or advisor')
+_PROGRAM_KNOWLEDGE_SITE_VERBOSE = translation.ugettext(
+    'Google site / blog post')
+_PROGRAM_KNOWLEDGE_FRIEND_VERBOSE = translation.ugettext(
+    'From a friend / classmate / family member')
+_PROGRAM_KNOWLEDGE_INTERNET_VERBOSE = translation.ugettext(
+    'Internet')
+_PROGRAM_KNOWLEDGE_OTHER_VERBOSE = translation.ugettext(
+    'Other')
+
+PROGRAM_KNOWLEDGE_DEFAULT_CHOICE = (('', _PROGRAM_KNOWLEDGE_DEFAULT_VERBOSE),)
+
+PROGRAM_KNOWLEDGE_CHOICES = (
+    (_PROGRAM_KNOWLEDGE_SCHOOL_ID, _PROGRAM_KNOWLEDGE_SCHOOL_VERBOSE),
+    (_PROGRAM_KNOWLEDGE_SITE_ID, _PROGRAM_KNOWLEDGE_SITE_VERBOSE),
+    (_PROGRAM_KNOWLEDGE_FRIEND_ID, _PROGRAM_KNOWLEDGE_FRIEND_VERBOSE),
+    (_PROGRAM_KNOWLEDGE_INTERNET_ID, _PROGRAM_KNOWLEDGE_INTERNET_VERBOSE),
+    (_PROGRAM_KNOWLEDGE_OTHER_ID, _PROGRAM_KNOWLEDGE_OTHER_VERBOSE))
+
 _DEGREE_UNDERGRADUATE_ID = 'undergraduate'
 _DEGREE_MASTERS_ID = 'masters'
 _DEGREE_PHD_ID = 'phd'
@@ -374,7 +401,8 @@
 
 _PROFILE_PROPERTIES_FORM_KEYS = [
     'public_name', 'photo_url', 'first_name', 'last_name', 'birth_date',
-    'tee_style', 'tee_size', 'gender', 'terms_of_service', 'program_knowledge']
+    'tee_style', 'tee_size', 'gender', 'terms_of_service', 'program_knowledge',
+    'program_knowledge_other']
 
 _CONTACT_PROPERTIES_FORM_KEYS = ['web_page', 'blog', 'email', 'phone']
 
@@ -611,7 +639,11 @@
   program_knowledge = django_forms.CharField(
       required=True, label=PROGRAM_KNOWLEDGE_LABEL,
       help_text=PROGRAM_KNOWLEDGE_HELP_TEXT,
-      widget=django_forms.Textarea())
+      widget=django_forms.Select(choices=(
+        PROGRAM_KNOWLEDGE_DEFAULT_CHOICE + PROGRAM_KNOWLEDGE_CHOICES)))
+
+  program_knowledge_other = django_forms.CharField(
+      required=False, widget=django_forms.Textarea())
 
   terms_of_service = django_forms.BooleanField(
       required=True, label=TERMS_OF_SERVICE_LABEL)
@@ -698,6 +730,7 @@
     self.fields['tee_size'].group = _OTHER_INFORMATION_GROUP
     self.fields['gender'].group = _OTHER_INFORMATION_GROUP
     self.fields['program_knowledge'].group = _OTHER_INFORMATION_GROUP
+    self.fields['program_knowledge_other'].group = _OTHER_INFORMATION_GROUP
 
     # remove terms of service field if no document is defined
     if not self.terms_of_service:
@@ -760,6 +793,25 @@
     # get rid of the regular field; there is no need to keep it
     del self.fields['terms_of_service']
 
+  def clean(self):
+    """Perform validation that require access to multiple fields
+    from the form at once.
+
+    Returns:
+      Cleaned data dictionary
+    """
+    # Check if program knowledge is other and
+    # program_knowledge_other is empty
+    if self.cleaned_data['program_knowledge'] == _PROGRAM_KNOWLEDGE_OTHER_ID:
+      if not self.cleaned_data['program_knowledge_other']:
+        self._errors['program_knowledge'] = self.error_class(
+          ['Field is required.'])
+        del self.cleaned_data['program_knowledge']
+      else:
+        self.cleaned_data['program_knowledge'] = (
+          self.cleaned_data['program_knowledge_other'])
+    return self.cleaned_data
+
   def clean_user_id(self):
     """Cleans user_id field.
 
@@ -1081,6 +1133,8 @@
     None: _GENDER_NOT_DISCLOSED_VERBOSE,
     }
 
+_PROGRAM_KNOWLEDGE_ENUM_TO_VERBOSE_MAP = dict(PROGRAM_KNOWLEDGE_CHOICES)
+
 _DEGREE_ID_TO_ENUM_LINK = (
     (_DEGREE_UNDERGRADUATE_ID, education_model.Degree.UNDERGRADUATE),
     (_DEGREE_MASTERS_ID, education_model.Degree.MASTERS),
@@ -1114,7 +1168,6 @@
       profile_model.Profile.program_knowledge._name:
           form_data.get('program_knowledge'),
       }
-
   if 'tee_style' in form_data:
     properties[profile_model.Profile.tee_style._name] = (
         _TEE_STYLE_ID_TO_ENUM_MAP[form_data['tee_style']])
@@ -1307,7 +1360,7 @@
       key: profile_properties.get(key)
       for key in [
           'first_name', 'last_name', 'photo_url', 'birth_date',
-          'public_name', 'program_knowledge']
+          'public_name']
       }
 
   # terms of service information
@@ -1341,6 +1394,14 @@
   form_data['gender'] = (
       _GENDER_ENUM_TO_ID_MAP[profile_properties['gender']])
 
+  program_knowledge = profile_properties.get('program_knowledge')
+  if program_knowledge not in (
+    [choice_id for choice_id, _ in PROGRAM_KNOWLEDGE_CHOICES]):
+    form_data['program_knowledge'] = _PROGRAM_KNOWLEDGE_OTHER_ID
+    form_data['program_knowledge_other'] = program_knowledge
+  else:
+    form_data['program_knowledge'] = program_knowledge
+
   # student information
   if profile_properties.get(profile_model.Profile.student_data._name):
     form_data.update(_adaptStudentDataPropertiesForForm(
diff --git a/app/soc/models/seed_db.py b/app/soc/models/seed_db.py
index ac01953..2606f86 100644
--- a/app/soc/models/seed_db.py
+++ b/app/soc/models/seed_db.py
@@ -375,7 +375,7 @@
       'residential_address' : address_properties,
       'shipping_address' : address_properties,
       'birth_date' : datetime.date.today() - gsoc_delta,
-      'program_knowledge' : 'Friend referral',
+      'program_knowledge' : 'friend',
       }
   profile = profile_model.Profile(**profile_properties)
 
@@ -491,7 +491,7 @@
       'tee_size': profile_model.TeeSize.L,
       'tee_style': profile_model.TeeStyle.MALE,
       'gender' : profile_model.Gender.MALE,
-      'program_knowledge': 'Friend referral.',
+      'program_knowledge': 'friend',
       'student_data' : student_data,
       'accepted_tos' : [ndb.Key.from_old_key(gsoc2015.student_agreement.key())],
       }
diff --git a/app/summerofcode/views/profile.py b/app/summerofcode/views/profile.py
index a33e321..a1aefd8 100644
--- a/app/summerofcode/views/profile.py
+++ b/app/summerofcode/views/profile.py
@@ -142,7 +142,15 @@
     fields[profile_view.TEE_SIZE_LABEL] = profile.tee_size
     fields[profile_view.GENDER_LABEL] = (
         profile_view._GENDER_ENUM_TO_VERBOSE_MAP[profile.gender])
-    fields[profile_view.PROGRAM_KNOWLEDGE_LABEL] = profile.program_knowledge
+
+    if profile.program_knowledge not in (
+      [choice_id for choice_id, _ in profile_view.PROGRAM_KNOWLEDGE_CHOICES]):
+      fields[profile_view.PROGRAM_KNOWLEDGE_LABEL] = profile.program_knowledge
+    else:
+      fields[profile_view.PROGRAM_KNOWLEDGE_LABEL] = (
+        profile_view._PROGRAM_KNOWLEDGE_ENUM_TO_VERBOSE_MAP[(
+          profile.program_knowledge)])
+
     groups.append(
         readonly.Group(
             data, 'summerofcode/readonly/_group.html',
diff --git a/seeder/profile.py b/seeder/profile.py
index bd91614..e544bd1 100644
--- a/seeder/profile.py
+++ b/seeder/profile.py
@@ -85,6 +85,7 @@
       'residential_address': residential_address,
       'tee_style': profile_model.TeeStyle.MALE,
       'tee_size': profile_model.TeeSize.M,
+      'program_knowledge': 'friend',
       'mentor_for': list(set(mentor_for + admin_for)),
       'admin_for': admin_for,
       'contact': contact,
diff --git a/tests/app/summerofcode/views/test_profile.py b/tests/app/summerofcode/views/test_profile.py
index b3b0507..9ba0c81 100644
--- a/tests/app/summerofcode/views/test_profile.py
+++ b/tests/app/summerofcode/views/test_profile.py
@@ -49,7 +49,7 @@
 TEST_GENDER = profile_view._GENDER_FEMALE_ID
 TEST_LAST_NAME = 'Test Last'
 TEST_PHONE = '1234567890'
-TEST_PROGRAM_KNOWLEDGE = u'Test program knowledge'
+TEST_PROGRAM_KNOWLEDGE = profile_view._PROGRAM_KNOWLEDGE_FRIEND_ID
 TEST_PUBLIC_NAME = 'Test Public Name'
 TEST_RESIDENTIAL_STREET = 'Test Street'
 TEST_RESIDENTIAL_STREET_EXTRA = 'Test Street Extra'
@@ -580,7 +580,7 @@
 
     self.assertEqual(profile.birth_date, TEST_BIRTH_DATE)
     # : TODO(daniel): handle program_knowledge
-    # self.assertEqual(profile.program_knowledge, TEST_PROGRAM_KNOWLEDGE)
+    self.assertEqual(profile.program_knowledge, TEST_PROGRAM_KNOWLEDGE)
     self.assertEqual(profile.tee_style, profile_model.TeeStyle.FEMALE)
     self.assertEqual(profile.tee_size, profile_model.TeeSize.M)
 
diff --git a/tests/functional/test_gsoc_student_registration.py b/tests/functional/test_gsoc_student_registration.py
index 6e65b03..0bda9dd 100644
--- a/tests/functional/test_gsoc_student_registration.py
+++ b/tests/functional/test_gsoc_student_registration.py
@@ -59,7 +59,7 @@
         {"Object":"T_shirt_style", "Identification":"tee_style", "Value":"female"},
         {"Object":"T_shirt_size", "Identification":"tee_size", "Value":"m"},
         {"Object":"Gender", "Identification":"gender", "Value":"female"},
-        {"Object":"How_did_you_hear_about_gsoc", "Identification":"melange-program_knowledge-textarea", "Value":"friends"},
+        {"Object":"How_did_you_hear_about_gsoc", "Identification":"program_knowledge", "Value":"friend"},
         {"Object":"I_agree", "Identification":"agreed_to_tos", "Value":None},
         {"Object":"Notify_to_new_public_comments", "Identification":"notify_public_comments", "Value":None},
         {"Object":"School_name", "Identification":"school_name", "Value":"Delhi University"},
@@ -149,8 +149,8 @@
     # Select gender as female.
     self.setDropDownList("Gender")
 
-    # Fill the text area.
-    self.writeTextField("How_did_you_hear_about_gsoc")
+    # Select program knowledge as friend.
+    self.setDropDownList("How_did_you_hear_about_gsoc")
 
     # Enter school name.
     self.writeTextField("School_name")