Merge branch 'daniel/code-sample'
diff --git a/app/soc/modules/gsoc/callback.py b/app/soc/modules/gsoc/callback.py
index 3e1eb38..8c9d4bb 100644
--- a/app/soc/modules/gsoc/callback.py
+++ b/app/soc/modules/gsoc/callback.py
@@ -94,6 +94,7 @@
     self.views.append(program.GSoCProgramMessagesPage())
     self.views.append(program.TimelinePage())
     self.views.append(program.UploadSchoolsPage())
+    self.views.append(project_details.PROJECT_CODE_SAMPLES_PAGE)
     self.views.append(project_details.CodeSampleDeleteFilePost())
     self.views.append(project_details.CodeSampleDownloadFileGet())
     self.views.append(project_details.CodeSampleUploadFilePost())
diff --git a/app/soc/modules/gsoc/views/helper/access_checker.py b/app/soc/modules/gsoc/views/helper/access_checker.py
index 19c0698..38da1cf 100644
--- a/app/soc/modules/gsoc/views/helper/access_checker.py
+++ b/app/soc/modules/gsoc/views/helper/access_checker.py
@@ -447,10 +447,9 @@
           message=DEF_ORG_APP_NOT_ACCEPTED % (app_record.org_id))
 
   def isProjectCompleted(self):
-    """Checks whether the project specified in the request is completed.
-    """
-    if len(self.data.url_project.passed_evaluations) < \
-        project_logic.NUMBER_OF_EVALUATIONS:
+    """Checks whether the project specified in the request is completed."""
+    if (len(self.data.url_ndb_project.passed_evaluations)
+        < project_logic.NUMBER_OF_EVALUATIONS):
       raise exception.Forbidden(message=DEF_PROJECT_NOT_COMPLETED)
 
   def canStudentUpdateProposal(self):
diff --git a/app/soc/modules/gsoc/views/project_details.py b/app/soc/modules/gsoc/views/project_details.py
index 5e2ae62..7603861 100644
--- a/app/soc/modules/gsoc/views/project_details.py
+++ b/app/soc/modules/gsoc/views/project_details.py
@@ -26,12 +26,13 @@
 from melange.logic import profile as profile_logic
 from melange.logic import user as user_logic
 from melange.models import profile as profile_model
+from melange.request import access
 from melange.request import exception
 from melange.request import links
 
+from soc.views import base as soc_base
 from soc.views.helper import blobstore as bs_helper
 from soc.views.helper import url_patterns
-from soc.views.helper.access_checker import isSet
 from soc.views.template import Template
 from soc.views.toggle_button import ToggleButtonTemplate
 
@@ -40,10 +41,17 @@
 from soc.modules.gsoc.views import assign_mentor
 from soc.modules.gsoc.views import base
 from soc.modules.gsoc.views import forms as gsoc_forms
-from soc.modules.gsoc.views.helper import url_names
+from soc.modules.gsoc.views.helper import url_names as soc_url_names
 from soc.modules.gsoc.views.helper import url_patterns as gsoc_url_patterns
 
 from summerofcode.models import project as project_model
+from summerofcode.request import access as soc_access
+from summerofcode.request import error
+from summerofcode.request import render
+from summerofcode.views.helper import urls
+
+
+_PROJECT_DETAILS_PAGE_NAME = ugettext('Code samples')
 
 
 class CodeSampleUploadFileForm(gsoc_forms.GSoCModelForm):
@@ -98,7 +106,11 @@
     """Builds a list containing the info related to each code sample.
     """
     code_samples = []
-    sources = self.data.url_project.codeSamples()
+
+    query = GSoCCodeSample.all()
+    query.ancestor(self.data.url_ndb_project.key.to_old_key())
+    sources = query.fetch(1000)
+
     for source in sorted(sources, key=lambda e: e.submitted_on):
       code_sample = {
           'entity': source
@@ -117,11 +129,11 @@
   def context(self):
     """See template.Template.context for specification."""
     code_sample_download_url = links.LINKER.userId(
-        self.data.url_profile.key(), self.data.url_project.key().id(),
-        url_names.GSOC_PROJECT_CODE_SAMPLE_DOWNLOAD)
+        self.data.url_ndb_profile.key, self.data.url_ndb_project.key.id(),
+        soc_url_names.GSOC_PROJECT_CODE_SAMPLE_DOWNLOAD)
     code_sample_delete_file_action = links.LINKER.userId(
-        self.data.url_profile.key(), self.data.url_project.key().id(),
-        url_names.GSOC_PROJECT_CODE_SAMPLE_DELETE)
+        self.data.url_ndb_profile.key, self.data.url_ndb_project.key.id(),
+        soc_url_names.GSOC_PROJECT_CODE_SAMPLE_DELETE)
     return {
         'code_samples': self._buildContextForExistingCodeSamples(),
         'code_sample_download_url': code_sample_download_url,
@@ -135,40 +147,15 @@
     return 'modules/gsoc/project_details/_list_code_samples.html'
 
 
-class UploadCodeSamples(Template):
-  """Template that contains a form to upload code samples.
-  """
-
-  def context(self):
-    """See template.Template.context for specification."""
-    code_sample_upload_file_action = links.LINKER.userId(
-        self.data.url_profile.key(), self.data.url_project.key().id(),
-        url_names.GSOC_PROJECT_CODE_SAMPLE_UPLOAD)
-
-    context = {
-        'code_sample_upload_file_form': CodeSampleUploadFileForm(),
-        'code_sample_upload_file_action': code_sample_upload_file_action,
-        }
-
-    if self.data.GET.get('file', None) == '0':
-      context['code_sample_upload_file_form'].addFileRequiredError()
-
-    return context
-
-  def templatePath(self):
-    """Returns the path to the template that should be used in render().
-    """
-    return 'modules/gsoc/project_details/_upload_code_samples.html'
-
-
 class CodeSampleUploadFilePost(base.GSoCRequestHandler):
   """Handler for POST requests to upload files with code samples."""
 
   def djangoURLPatterns(self):
     """Returns the list of tuples for containing URL to view method mapping."""
     return [
-        gsoc_url_patterns.url(r'project/code_sample/upload/%s$' % url_patterns.USER_ID, self,
-            name=url_names.GSOC_PROJECT_CODE_SAMPLE_UPLOAD)
+        gsoc_url_patterns.url(
+            r'project/code_sample/upload/%s$' % url_patterns.USER_ID, self,
+            name=urls.UrlNames.PROJECT_CODE_SAMPLE_UPLOAD)
     ]
 
   def checkAccess(self, data, check, mutator):
@@ -177,8 +164,6 @@
 
   def post(self, data, check, mutator):
     """Post handler for the code sample upload file."""
-    assert isSet(data.url_project)
-
     form = CodeSampleUploadFileForm(
         data=data.POST, files=data.request.file_uploads)
 
@@ -187,33 +172,21 @@
       for blob_info in data.request.file_uploads.itervalues():
         blob_info.delete()
       url = links.LINKER.userId(
-          data.url_profile.key(), data.url_project.key().id(),
-          url_names.GSOC_PROJECT_UPDATE)
+          data.url_ndb_profile.key, data.url_ndb_project.key.id(),
+          urls.UrlNames.PROJECT_CODE_SAMPLES)
       # TODO(daniel): GET params should be handled automatically
       url = url + '?file=0'
       return http.HttpResponseRedirect(url)
 
-    org_key = GSoCProject.org.get_value_for_datastore(data.url_project)
-    form.cleaned_data['user'] = data.user
-    form.cleaned_data['org'] = org_key
-    form.cleaned_data['program'] = data.url_project.program
+    form.cleaned_data['user'] = data.ndb_user.key.to_old_key()
+    form.cleaned_data['org'] = data.url_ndb_project.organization.to_old_key()
+    form.cleaned_data['program'] = data.url_ndb_project.program.to_old_key()
 
-    project_key = data.url_project.key()
-    code_sample = form.create(commit=False, parent=project_key)
-
-    def txn():
-      code_sample.put()
-
-      project = GSoCProject.get(project_key)
-      if not project.code_samples_submitted:
-        project.code_samples_submitted = True
-        project.put()
-
-    db.run_in_transaction(txn)
+    form.create(commit=True, parent=data.url_ndb_project.key.to_old_key())
 
     url = links.LINKER.userId(
-        data.url_profile.key(), data.url_project.key().id(),
-        url_names.GSOC_PROJECT_UPDATE)
+        data.url_ndb_profile.key, data.url_ndb_project.key.id(),
+        urls.UrlNames.PROJECT_CODE_SAMPLES)
     return http.HttpResponseRedirect(url)
 
 
@@ -223,8 +196,9 @@
   def djangoURLPatterns(self):
     """Returns the list of tuples for containing URL to view method mapping."""
     return [
-        gsoc_url_patterns.url(r'project/code_sample/download/%s$' % url_patterns.USER_ID, self,
-            name=url_names.GSOC_PROJECT_CODE_SAMPLE_DOWNLOAD)
+        gsoc_url_patterns.url(
+            r'project/code_sample/download/%s$' % url_patterns.USER_ID, self,
+            name=soc_url_names.GSOC_PROJECT_CODE_SAMPLE_DOWNLOAD)
     ]
 
   def checkAccess(self, data, check, mutator):
@@ -232,11 +206,10 @@
 
   def get(self, data, check, mutator):
     """Get handler for the code sample download file."""
-    assert isSet(data.url_project)
-
     try:
       id_value = int(data.request.GET['id'])
-      code_sample = GSoCCodeSample.get_by_id(id_value, data.url_project)
+      code_sample = GSoCCodeSample.get_by_id(
+          id_value, parent=data.url_ndb_project.key.to_old_key())
       if not code_sample or not code_sample.upload_of_work:
         raise exception.BadRequest(
             message='Requested project or code sample not found')
@@ -255,8 +228,9 @@
   def djangoURLPatterns(self):
     """Returns the list of tuples for containing URL to view method mapping."""
     return [
-        gsoc_url_patterns.url(r'project/code_sample/delete/%s$' % url_patterns.USER_ID, self,
-            name=url_names.GSOC_PROJECT_CODE_SAMPLE_DELETE)
+        gsoc_url_patterns.url(
+            r'project/code_sample/delete/%s$' % url_patterns.USER_ID, self,
+            name=soc_url_names.GSOC_PROJECT_CODE_SAMPLE_DELETE)
     ]
 
   def checkAccess(self, data, check, mutator):
@@ -265,11 +239,10 @@
 
   def post(self, data, check, mutator):
     """Get handler for the code sample delete file."""
-    assert isSet(data.url_project)
-
     try:
       id_value = int(data.request.POST['id'])
-      code_sample = GSoCCodeSample.get_by_id(id_value, data.url_project)
+      code_sample = GSoCCodeSample.get_by_id(
+          id_value, parent=data.url_ndb_project.key.to_old_key())
 
       if not code_sample:
         raise exception.BadRequest(message='Requested code sample not found')
@@ -282,16 +255,11 @@
           # this is executed outside of transaction
           upload_of_work.delete()
 
-        if data.url_project.countCodeSamples() <= 1:
-          project = GSoCProject.get(data.url_project.key())
-          project.code_samples_submitted = False
-          project.put()
-
       db.run_in_transaction(txn)
 
       url = links.LINKER.userId(
-          data.url_profile.key(), data.url_project.key().id(),
-          url_names.GSOC_PROJECT_UPDATE)
+          data.url_ndb_profile.key, data.url_ndb_project.key.id(),
+          urls.UrlNames.PROJECT_CODE_SAMPLES)
       return http.HttpResponseRedirect(url)
     except KeyError:
       raise exception.BadRequest(message='id argument missing in POST data')
@@ -449,3 +417,67 @@
   def get(self, data, check, mutator):
     """Special Handler for HTTP GET since this view only handles POST."""
     raise exception.MethodNotAllowed()
+
+
+class ProjectCodeSamplesPage(soc_base.RequestHandler):
+  """View to list and update code samples for the project."""
+
+  access_checker = access.ConjuctionAccessChecker([
+      access.IS_URL_USER_ACCESS_CHECKER,
+      soc_access.STUDENTS_ANNOUNCED_ACCESS_CHECKER])
+
+  def __init__(self, initializer, linker, renderer, error_handler,
+      url_pattern_constructor, url_names, template_path):
+    """Initializes a new instance of the request handler for the specified
+    parameters.
+
+    Args:
+      initializer: Implementation of initialize.Initializer interface.
+      linker: Instance of links.Linker class.
+      renderer: Implementation of render.Renderer interface.
+      error_handler: Implementation of error.ErrorHandler interface.
+      url_pattern_constructor:
+        Implementation of url_patterns.UrlPatternConstructor.
+      url_names: Instance of url_names.UrlNames.
+      template_path: The path of the template to be used.
+    """
+    super(ProjectCodeSamplesPage, self).__init__(
+        initializer, linker, renderer, error_handler)
+    self.url_pattern_constructor = url_pattern_constructor
+    self.url_names = url_names
+    self.template_path = template_path
+
+  def djangoURLPatterns(self):
+    """See base.RequestHandler.djangoURLPatterns for specification."""
+    return [
+        self.url_pattern_constructor.construct(
+            r'project/code_samples/%s$' % url_patterns.USER_ID,
+            self, name=self.url_names.PROJECT_CODE_SAMPLES)
+    ]
+
+  def templatePath(self):
+    """See base.RequestHandler.templatePath for specification."""
+    return self.template_path
+
+  def context(self, data, check, mutator):
+    """See base.RequestHandler.context for specification."""
+    context = {
+        'code_sample_upload_file_form': CodeSampleUploadFileForm(),
+        'code_sample_upload_file_action': blobstore.create_upload_url(
+            self.linker.userId(
+                data.url_ndb_profile.key,
+                data.url_ndb_project.key.id(),
+                self.url_names.PROJECT_CODE_SAMPLE_UPLOAD)),
+        'page_name': _PROJECT_DETAILS_PAGE_NAME,
+        'code_samples_list': ListCodeSamples(data, True)
+        }
+
+    if data.GET.get('file', None) == '0':
+      context['code_sample_upload_file_form'].addFileRequiredError()
+
+    return context
+
+PROJECT_CODE_SAMPLES_PAGE = ProjectCodeSamplesPage(
+    base._GSOC_INITIALIZER, links.LINKER, render.SOC_RENDERER,
+    error.SOC_ERROR_HANDLER, gsoc_url_patterns.SOC_URL_PATTERN_CONSTRUCTOR,
+    urls.UrlNames, 'summerofcode/project/code_samples.html')
diff --git a/app/soc/views/helper/access_checker.py b/app/soc/views/helper/access_checker.py
index 94bc3a4..b7e0169 100644
--- a/app/soc/views/helper/access_checker.py
+++ b/app/soc/views/helper/access_checker.py
@@ -32,6 +32,7 @@
 
 from summerofcode.logic import survey as survey_logic
 
+
 DEF_AGREE_TO_TOS = ugettext(
     'You must agree to the <a href="%(tos_link)s">site-wide Terms of'
     ' Service</a> in your <a href="/user/edit_profile">User Profile</a>'
@@ -678,32 +679,6 @@
           }
       raise exception.Forbidden(message=error_msg)
 
-  def canStudentUpdateProject(self):
-    """Checks if the student can edit the project details.
-    """
-    assert isSet(self.data.program)
-    assert isSet(self.data.timeline)
-
-    self.isProjectInURLValid()
-
-    # check if the timeline allows updating project
-    self.isProgramVisible()
-    self.acceptedStudentsAnnounced()
-
-    # check if the project belongs to the current user
-    expected_profile_key = self.data.url_project.parent_key()
-    if expected_profile_key != self.data.ndb_profile.key.to_old_key():
-      error_msg = DEF_ENTITY_DOES_NOT_BELONG_TO_YOU % {
-          'name': 'project'
-          }
-      raise exception.Forbidden(message=error_msg)
-
-    # check if the status allows the project to be updated
-    if self.data.url_project.status in ['invalid', 'withdrawn', 'failed']:
-      raise exception.Forbidden(message=DEF_CANNOT_UPDATE_ENTITY % {
-          'name': 'project'
-          })
-
   def isSurveyActive(self, survey, show_url=None):
     """Checks if the survey in the request data is active.
 
diff --git a/app/summerofcode/content/html/summerofcode/project/code_samples.html b/app/summerofcode/content/html/summerofcode/project/code_samples.html
new file mode 100644
index 0000000..a9b560b
--- /dev/null
+++ b/app/summerofcode/content/html/summerofcode/project/code_samples.html
@@ -0,0 +1,40 @@
+{% extends "modules/gsoc/base.html" %}
+{% comment %}
+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.
+{% endcomment %}
+
+{% block stylesheets %}
+  {{ block.super }}
+  <link rel="stylesheet" type="text/css" media="screen" href="/soc/content/{{ app_version }}/css/gsoc/buttons.css" />
+  <link rel="stylesheet" type="text/css" media="screen" href="/soc/content/{{ app_version }}/css/gsoc/forms.css" />
+  <link rel="stylesheet" type="text/css" media="screen" href="/soc/content/{{ app_version }}/css/gsoc/tables.css" />
+  <link rel="stylesheet" type="text/css" media="screen" href="/soc/content/{{ app_version }}/css/gsoc/uniform.default.css" />
+{% endblock stylesheets %}
+
+{% block page_content %}
+
+<div id="block-project-upload-code-sample">
+  <form action="{{ code_sample_upload_file_action }}" method="post" enctype="multipart/form-data" id="file-form" class="form-register">
+    <h2 id="form-upload-title">Upload code samples</h2>
+    <h3 id="form-upload-help">Upload your final code samples, then click Submit.</h3>
+    {{ code_sample_upload_file_form.render }}
+    <div id="form-upload-button-row" class="row button-row">
+      <input id="form-upload-submit" type="submit" value="Submit File" class="submit" />
+    </div>
+  </form>
+</div>
+
+<br>
+{{ code_samples_list.render }}
+
+{% endblock %}
diff --git a/app/summerofcode/content/html/summerofcode/project/project_details.html b/app/summerofcode/content/html/summerofcode/project/project_details.html
index 79dded2..946d5e9 100644
--- a/app/summerofcode/content/html/summerofcode/project/project_details.html
+++ b/app/summerofcode/content/html/summerofcode/project/project_details.html
@@ -15,6 +15,7 @@
 
 {% block stylesheets %}
   {{ block.super }}
+  <link rel="stylesheet" type="text/css" media="screen" href="/soc/content/{{ app_version }}/css/gsoc/tables.css" />
   <link rel="stylesheet" type="text/css" media="screen" href="/soc/content/{{ app_version }}/css/gsoc/buttons.css" />
   <link rel="stylesheet" type="text/css" media="screen" href="/soc/content/{{ app_version }}/css/gsoc/forms.css" />
   <link rel="stylesheet" type="text/css" media="screen" href="/soc/content/{{ app_version }}/css/gsoc/uniform.default.css" />
@@ -31,9 +32,14 @@
 </h1>
 <h2 id="project-page-student-name">
 {{ student_name }}
-{% if edit_url %}
-  <div id="edit-page" class="project-edit-link">
-    <a href="{{ edit_url }}">Edit project</a>
+{% if edit_url or code_samples_url %}
+  <div id="manage-links" class="project-edit-link">
+    {% if edit_url %}
+      <a href="{{ edit_url }}">Edit project</a>
+    {% endif %}
+    {% if code_samples_url %}
+      <a href="{{ code_samples_url }}">Code samples</a>
+    {% endif %}
   </div>
 {% endif %}
 </h2>
@@ -69,6 +75,11 @@
   </div>
 {% endif %}
 
+{% if code_samples_list %}
+  <br>
+  {{ code_samples_list.render }}
+{% endif %}
+
 {% endblock %}
 
 {% block dependencies %}
diff --git a/app/summerofcode/views/helper/urls.py b/app/summerofcode/views/helper/urls.py
index c0561e1..c647068 100644
--- a/app/summerofcode/views/helper/urls.py
+++ b/app/summerofcode/views/helper/urls.py
@@ -44,6 +44,8 @@
   PROFILE_REGISTER_AS_STUDENT = 'soc_profile_register_as_student'
   PROFILE_SHOW = 'soc_profile_show'
   PROJECT_ASSIGN_MENTORS = 'soc_project_assign_mentors'
+  PROJECT_CODE_SAMPLES = 'soc_project_code_samples'
+  PROJECT_CODE_SAMPLE_UPLOAD = 'soc_project_code_sample_upload'
   PROJECT_DETAILS = 'soc_project_details'
   PROJECT_EDIT = 'soc_project_edit'
   PROJECT_LIST_PUBLIC = 'soc_project_list_public'
diff --git a/app/summerofcode/views/project.py b/app/summerofcode/views/project.py
index 9b421e0..464e5e9 100644
--- a/app/summerofcode/views/project.py
+++ b/app/summerofcode/views/project.py
@@ -23,9 +23,11 @@
 from melange.request import exception
 
 from soc.logic import cleaning
+from soc.modules.gsoc.models import code_sample as code_sample_model
 from soc.modules.gsoc.views import assign_mentor
 from soc.modules.gsoc.views import forms as gsoc_forms
 from soc.modules.gsoc.views import base as gsoc_base
+from soc.modules.gsoc.views import project_details as old_project_view
 from soc.modules.gsoc.views.helper import url_patterns as soc_url_patterns
 from soc.views import base
 from soc.views import base_templates
@@ -299,8 +301,27 @@
             and data.ndb_profile.key == data.url_ndb_project.key.parent())
         else None)
 
+    # TODO(daniel): replace it with new updated code samples at some point
+    query = code_sample_model.GSoCCodeSample.all()
+    query.ancestor(data.url_ndb_project.key.to_old_key())
+    code_samples_list = (
+        old_project_view.ListCodeSamples(data, False)
+        if query.count() > 0 else None)
+
+    code_samples_url = (
+        links.SOC_LINKER.userId(
+            data.url_ndb_profile.key, data.url_ndb_project.key.id(),
+            self.url_names.PROJECT_CODE_SAMPLES)
+        # TODO(daniel): move this condition to a logic function
+        if (len(data.url_ndb_project.passed_evaluations) >= 2
+            and data.ndb_profile
+            and data.ndb_profile.key == data.url_ndb_project.key.parent())
+        else None)
+
     return {
         'abstract': data.url_ndb_project.abstract,
+        'code_samples_list': code_samples_list,
+        'code_samples_url': code_samples_url,
         'content': data.url_ndb_project.content,
         'edit_url': edit_url,
         'manage_actions': manage_actions,