Repository: comic/grand-challenge.org

Issue:
Submission create view for legacy results
Add option for challenge administrators to set the creator for the submission, creating a new user if needed?


Discussion:

Resolving patch:
diff --git a/app/grandchallenge/evaluation/forms.py b/app/grandchallenge/evaluation/forms.py
--- a/app/grandchallenge/evaluation/forms.py
+++ b/app/grandchallenge/evaluation/forms.py
@@ -79,31 +79,51 @@ def __init__(self, *args, **kwargs):
         display_comment_field kwarg
         """
         display_comment_field = kwargs.pop('display_comment_field', False)
+
         allow_supplementary_file = kwargs.pop(
             'allow_supplementary_file', False
         )
+
         require_supplementary_file = kwargs.pop(
             'require_supplementary_file', False
         )
+
         supplementary_file_label = kwargs.pop('supplementary_file_label', '')
+
         supplementary_file_help_text = kwargs.pop(
             'supplementary_file_help_text', ''
         )
+
         super(SubmissionForm, self).__init__(*args, **kwargs)
+
         if not display_comment_field:
             del self.fields['comment']
+
         if supplementary_file_label:
             self.fields['supplementary_file'].label = supplementary_file_label
+
         if supplementary_file_help_text:
             self.fields[
                 'supplementary_file'
             ].help_text = supplementary_file_help_text
+
         if require_supplementary_file:
             self.fields['supplementary_file'].required = True
         elif not allow_supplementary_file:
             del self.fields['supplementary_file']
+
         self.helper = FormHelper(self)
 
     class Meta:
         model = Submission
         fields = ('comment', 'supplementary_file', 'chunked_upload')
+
+class LegacySubmissionForm(SubmissionForm):
+    class Meta:
+        model = Submission
+        fields = (
+            'creator',
+            'comment',
+            'supplementary_file',
+            'chunked_upload',
+        )
diff --git a/app/grandchallenge/evaluation/urls.py b/app/grandchallenge/evaluation/urls.py
--- a/app/grandchallenge/evaluation/urls.py
+++ b/app/grandchallenge/evaluation/urls.py
@@ -18,6 +18,7 @@
     ResultDetail,
     ConfigUpdate,
     ResultUpdate,
+    LegacySubmissionCreate,
 )
 from grandchallenge.jqfileupload.forms import (
     test_upload_widget, test_upload_widget2,
@@ -44,6 +45,16 @@
         SubmissionCreate.as_view(),
         name='submission-create',
     ),
+    url(
+        r'^submissions/create-legacy/$',
+        LegacySubmissionCreate.as_view(),
+        name='submission-create-legacy',
+    ),
+    url(
+        f'^submissions/create-legacy/{submission_upload_widget.ajax_target_path}$',
+        submission_upload_widget.handle_ajax,
+        name='submission-upload-legacy-ajax',
+    ),
     url(
         f'^submissions/create/{submission_upload_widget.ajax_target_path}$',
         submission_upload_widget.handle_ajax,
@@ -73,6 +84,7 @@
         name='result-update',
     ),
 ]
+
 if settings.DEBUG:
     urlpatterns.append(
         url(
diff --git a/app/grandchallenge/evaluation/views.py b/app/grandchallenge/evaluation/views.py
--- a/app/grandchallenge/evaluation/views.py
+++ b/app/grandchallenge/evaluation/views.py
@@ -12,7 +12,7 @@
 )
 from grandchallenge.core.urlresolvers import reverse
 from grandchallenge.evaluation.forms import (
-    MethodForm, SubmissionForm, ConfigForm
+    MethodForm, SubmissionForm, ConfigForm, LegacySubmissionForm
 )
 from grandchallenge.evaluation.models import (
     Result, Submission, Job, Method, Config,
@@ -53,19 +53,21 @@ class MethodDetail(UserIsChallengeAdminMixin, DetailView):
     model = Method
 
 
-class SubmissionCreate(
-    UserIsChallengeParticipantOrAdminMixin, SuccessMessageMixin, CreateView
-):
+class SubmissionCreateBase(SuccessMessageMixin, CreateView):
+    """
+    This class has no permissions, do not use it directly! See the subclasses
+    """
     model = Submission
-    form_class = SubmissionForm
     success_message = (
         "Your submission was successful. "
         "Your result will appear on the leaderboard when it is ready."
     )
 
     def get_form_kwargs(self):
-        kwargs = super(SubmissionCreate, self).get_form_kwargs()
+        kwargs = super().get_form_kwargs()
+
         config = Config.objects.get(challenge=self.request.challenge)
+
         kwargs.update(
             {
                 'display_comment_field': config.allow_submission_comments,
@@ -75,20 +77,26 @@ def get_form_kwargs(self):
                 'supplementary_file_help_text': config.supplementary_file_help_text,
             }
         )
+
         return kwargs
 
     def get_context_data(self, **kwargs):
-        context = super(SubmissionCreate, self).get_context_data(**kwargs)
+        context = super().get_context_data(**kwargs)
+
         config = Config.objects.get(challenge=self.request.challenge)
+
         context.update(
             self.get_next_submission(max_subs=config.daily_submission_limit)
         )
+
         pending_jobs = Job.objects.filter(
             challenge=self.request.challenge,
             submission__creator=self.request.user,
             status__in=(Job.PENDING, Job.STARTED),
         ).count()
+
         context.update({'pending_jobs': pending_jobs})
+
         return context
 
     def get_next_submission(
@@ -107,6 +115,7 @@ def get_next_submission(
         """
         if now is None:
             now = timezone.now()
+
         subs = Submission.objects.filter(
             challenge=self.request.challenge,
             creator=self.request.user,
@@ -114,22 +123,30 @@ def get_next_submission(
         ).order_by(
             '-created'
         )
+
         try:
             next_sub_at = subs[max_subs - 1].created + period
         except (IndexError, AssertionError):
             next_sub_at = now
+
         return {
             'remaining_submissions': max_subs - len(subs),
             'next_submission_at': next_sub_at,
         }
 
     def form_valid(self, form):
-        form.instance.creator = self.request.user
+
+        if form.instance.creator is None:
+            form.instance.creator = self.request.user
+
         form.instance.challenge = self.request.challenge
+
         uploaded_file = form.cleaned_data['chunked_upload'][0]
+
         with uploaded_file.open() as f:
             form.instance.file.save(uploaded_file.name, File(f))
-        return super(SubmissionCreate, self).form_valid(form)
+
+        return super().form_valid(form)
 
     def get_success_url(self):
         return reverse(
@@ -138,6 +155,35 @@ def get_success_url(self):
         )
 
 
+class SubmissionCreate(
+    UserIsChallengeParticipantOrAdminMixin, SubmissionCreateBase
+):
+    form_class = SubmissionForm
+
+
+class LegacySubmissionCreate(UserIsChallengeAdminMixin, SubmissionCreateBase):
+    form_class = LegacySubmissionForm
+
+    def get_next_submission(
+        self,
+        *,
+        max_subs: int,
+        period: timedelta = timedelta(days=1),
+        now: datetime = None
+    ):
+        """
+        Admins should always be able to upload legacy results, so set the
+        remaining submissions to infinite.
+        """
+        if now is None:
+            now = timezone.now()
+
+        return {
+            'remaining_submissions': float('Inf'),
+            'next_submission_at': now,
+        }
+
+
 class SubmissionList(UserIsChallengeParticipantOrAdminMixin, ListView):
     model = Submission
 


Test patch:
diff --git a/app/tests/evaluation_tests/test_views.py b/app/tests/evaluation_tests/test_views.py
--- a/app/tests/evaluation_tests/test_views.py
+++ b/app/tests/evaluation_tests/test_views.py
@@ -169,6 +169,35 @@ def test_submission_create(client, TwoChallengeSets):
         client=client,
     )
 
+    response = get_view_for_user(
+        viewname='evaluation:submission-create',
+        challenge=TwoChallengeSets.ChallengeSet1.challenge,
+        user=TwoChallengeSets.ChallengeSet1.participant,
+        client=client,
+    )
+
+    assert response.status_code == 200
+    assert 'Creator' not in response.rendered_content
+
+
+@pytest.mark.django_db
+def test_legacy_submission_create(client, TwoChallengeSets):
+    validate_admin_only_view(
+        viewname='evaluation:submission-create-legacy',
+        two_challenge_set=TwoChallengeSets,
+        client=client,
+    )
+
+    response = get_view_for_user(
+        viewname='evaluation:submission-create-legacy',
+        challenge=TwoChallengeSets.ChallengeSet1.challenge,
+        user=TwoChallengeSets.admin12,
+        client=client,
+    )
+
+    assert response.status_code == 200
+    assert 'Creator' in response.rendered_content
+
 
 @pytest.mark.django_db
 def test_submission_time_limit(client, TwoChallengeSets):