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,