We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Make error message emails less verbose
The entire stacktrace is included right now.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `app/grandchallenge/evaluation/emails.py`
Content:
```
1 import json
2 
3 from django.conf import settings
4 from django.core.mail import send_mail
5 
6 from grandchallenge.core.urlresolvers import reverse
7 
8 
9 def send_failed_job_email(job):
10     message = (
11         f'Unfortunately the evaluation for the submission to '
12         f'{job.challenge.short_name} failed with an error. The error message '
13         f'is:\n\n'
14         f'{job.output}\n\n'
15         f'You may wish to try and correct this, or contact the challenge '
16         f'organizers. The following information may help them:\n'
17         f'User: {job.submission.creator.username}\n'
18         f'Job ID: {job.pk}\n'
19         f'Submission ID: {job.submission.pk}'
20     )
21     recipient_emails = [o.email for o in job.challenge.get_admins()]
22     recipient_emails.append(job.submission.creator.email)
23     for email in recipient_emails:
24         send_mail(
25             subject='Evaluation Failed',
26             message=message,
27             from_email=settings.DEFAULT_FROM_EMAIL,
28             recipient_list=[email],
29         )
30 
31 
32 def send_new_result_email(result):
33     recipient_emails = [o.email for o in result.challenge.get_admins()]
34     message = (
35         f'There is a new result for {result.challenge.short_name} from '
36         f'{result.job.submission.creator.username}. The following metrics '
37         f'were calculated:\n\n'
38         f'{json.dumps(result.metrics, indent=2)}\n\n'
39     )
40     if result.public:
41         leaderboard_url = reverse(
42             'evaluation:result-list',
43             kwargs={'challenge_short_name': result.challenge.short_name},
44         )
45         message += (
46             f'You can view the result on the leaderboard here: '
47             f'{leaderboard_url}'
48         )
49         recipient_emails.append(result.job.submission.creator.email)
50     else:
51         message += (
52             f'You can publish the result on the leaderboard here: '
53             f'{result.get_absolute_url()}'
54         )
55     for email in recipient_emails:
56         send_mail(
57             subject=f'New Result for {result.challenge.short_name}',
58             message=message,
59             from_email=settings.DEFAULT_FROM_EMAIL,
60             recipient_list=[email],
61         )
62
```

Path: `app/grandchallenge/evaluation/views.py`
Content:
```
1 from datetime import timedelta, datetime
2 from typing import Dict
3 
4 from django.contrib.messages.views import SuccessMessageMixin
5 from django.core.files import File
6 from django.db.models import Q
7 from django.utils import timezone
8 from django.views.generic import (CreateView, ListView, DetailView, UpdateView)
9 
10 from grandchallenge.core.permissions.mixins import (
11     UserIsChallengeAdminMixin, UserIsChallengeParticipantOrAdminMixin
12 )
13 from grandchallenge.core.urlresolvers import reverse
14 from grandchallenge.evaluation.forms import MethodForm, SubmissionForm
15 from grandchallenge.evaluation.models import (
16     Result, Submission, Job, Method, Config,
17 )
18 
19 
20 class ConfigUpdate(UserIsChallengeAdminMixin, SuccessMessageMixin, UpdateView):
21     model = Config
22     fields = (
23         'use_teams',
24         'daily_submission_limit',
25         'score_title',
26         'score_jsonpath',
27         'score_default_sort',
28         'extra_results_columns',
29         'allow_submission_comments',
30         'allow_supplementary_file',
31         'require_supplementary_file',
32         'supplementary_file_label',
33         'supplementary_file_help_text',
34         'show_supplementary_file_link',
35     )
36     success_message = "Configuration successfully updated"
37 
38     def get_object(self, queryset=None):
39         challenge = self.request.challenge
40         return challenge.evaluation_config
41 
42 
43 class MethodCreate(UserIsChallengeAdminMixin, CreateView):
44     model = Method
45     form_class = MethodForm
46 
47     def form_valid(self, form):
48         form.instance.creator = self.request.user
49         form.instance.challenge = self.request.challenge
50         uploaded_file = form.cleaned_data['chunked_upload'][0]
51         with uploaded_file.open() as f:
52             form.instance.image.save(uploaded_file.name, File(f))
53         return super(MethodCreate, self).form_valid(form)
54 
55 
56 class MethodList(UserIsChallengeAdminMixin, ListView):
57     model = Method
58 
59     def get_queryset(self):
60         queryset = super(MethodList, self).get_queryset()
61         return queryset.filter(challenge=self.request.challenge)
62 
63 
64 class MethodDetail(UserIsChallengeAdminMixin, DetailView):
65     model = Method
66 
67 
68 class SubmissionCreate(
69     UserIsChallengeParticipantOrAdminMixin, SuccessMessageMixin, CreateView
70 ):
71     model = Submission
72     form_class = SubmissionForm
73     success_message = (
74         "Your submission was successful. "
75         "Please keep checking this page for your result."
76     )
77 
78     def get_form_kwargs(self):
79         kwargs = super(SubmissionCreate, self).get_form_kwargs()
80         config = Config.objects.get(challenge=self.request.challenge)
81         kwargs.update(
82             {
83                 'display_comment_field': config.allow_submission_comments,
84                 'allow_supplementary_file': config.allow_supplementary_file,
85                 'require_supplementary_file': config.require_supplementary_file,
86                 'supplementary_file_label': config.supplementary_file_label,
87                 'supplementary_file_help_text': config.supplementary_file_help_text,
88             }
89         )
90         return kwargs
91 
92     def get_context_data(self, **kwargs):
93         context = super(SubmissionCreate, self).get_context_data(**kwargs)
94         config = Config.objects.get(challenge=self.request.challenge)
95         context.update(
96             self.get_next_submission(max_subs=config.daily_submission_limit)
97         )
98         pending_jobs = Job.objects.filter(
99             challenge=self.request.challenge,
100             submission__creator=self.request.user,
101             status__in=(Job.PENDING, Job.STARTED),
102         ).count()
103         context.update({'pending_jobs': pending_jobs})
104         return context
105 
106     def get_next_submission(
107         self,
108         *,
109         max_subs: int,
110         period: timedelta =timedelta(days=1),
111         now: datetime = None
112     ) -> Dict:
113         """
114         Determines the number of submissions left for the user in a given time
115         period, and when they can next submit.
116 
117         :return: A dictionary containing remaining_submissions (int) and
118         next_submission_at (datetime)
119         """
120         if now is None:
121             now = timezone.now()
122         subs = Submission.objects.filter(
123             challenge=self.request.challenge,
124             creator=self.request.user,
125             created__gte=now - period,
126         ).order_by(
127             '-created'
128         )
129         try:
130             next_sub_at = subs[max_subs - 1].created + period
131         except (IndexError, AssertionError):
132             next_sub_at = now
133         return {
134             'remaining_submissions': max_subs - len(subs),
135             'next_submission_at': next_sub_at,
136         }
137 
138     def form_valid(self, form):
139         form.instance.creator = self.request.user
140         form.instance.challenge = self.request.challenge
141         uploaded_file = form.cleaned_data['chunked_upload'][0]
142         with uploaded_file.open() as f:
143             form.instance.file.save(uploaded_file.name, File(f))
144         return super(SubmissionCreate, self).form_valid(form)
145 
146     def get_success_url(self):
147         return reverse(
148             'evaluation:job-list',
149             kwargs={'challenge_short_name': self.object.challenge.short_name},
150         )
151 
152 
153 class SubmissionList(UserIsChallengeParticipantOrAdminMixin, ListView):
154     model = Submission
155 
156     def get_queryset(self):
157         """ Admins see everything, participants just their submissions """
158         queryset = super(SubmissionList, self).get_queryset()
159         challenge = self.request.challenge
160         if challenge.is_admin(self.request.user):
161             return queryset.filter(challenge=self.request.challenge)
162 
163         else:
164             return queryset.filter(
165                 Q(challenge=self.request.challenge),
166                 Q(creator__pk=self.request.user.pk),
167             )
168 
169 
170 class SubmissionDetail(UserIsChallengeAdminMixin, DetailView):
171     # TODO - if participant: list only their submissions
172     model = Submission
173 
174 
175 class JobCreate(UserIsChallengeAdminMixin, CreateView):
176     model = Job
177     fields = '__all__'
178 
179 
180 class JobList(UserIsChallengeParticipantOrAdminMixin, ListView):
181     model = Job
182 
183     def get_queryset(self):
184         """ Admins see everything, participants just their jobs """
185         queryset = super(JobList, self).get_queryset()
186         queryset = queryset.select_related('result')
187         challenge = self.request.challenge
188         if challenge.is_admin(self.request.user):
189             return queryset.filter(challenge=self.request.challenge)
190 
191         else:
192             return queryset.filter(
193                 Q(challenge=self.request.challenge),
194                 Q(submission__creator__pk=self.request.user.pk),
195             )
196 
197 
198 class JobDetail(UserIsChallengeAdminMixin, DetailView):
199     # TODO - if participant: list only their jobs
200     model = Job
201 
202 
203 class ResultList(ListView):
204     model = Result
205 
206     def get_queryset(self):
207         queryset = super(ResultList, self).get_queryset()
208         queryset = queryset.select_related(
209             'job__submission__creator__user_profile'
210         )
211         return queryset.filter(
212             Q(challenge=self.request.challenge), Q(public=True)
213         )
214 
215 
216 class ResultDetail(DetailView):
217     model = Result
218 
219 
220 class ResultUpdate(UserIsChallengeAdminMixin, SuccessMessageMixin, UpdateView):
221     model = Result
222     fields = ('public',)
223     success_message = ('Result successfully updated.')
224
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/app/grandchallenge/evaluation/emails.py b/app/grandchallenge/evaluation/emails.py
--- a/app/grandchallenge/evaluation/emails.py
+++ b/app/grandchallenge/evaluation/emails.py
@@ -4,6 +4,7 @@
 from django.core.mail import send_mail
 
 from grandchallenge.core.urlresolvers import reverse
+from grandchallenge.evaluation.templatetags.evaluation_extras import user_error
 
 
 def send_failed_job_email(job):
@@ -11,7 +12,7 @@
         f'Unfortunately the evaluation for the submission to '
         f'{job.challenge.short_name} failed with an error. The error message '
         f'is:\n\n'
-        f'{job.output}\n\n'
+        f'{user_error(job.output)}\n\n'
         f'You may wish to try and correct this, or contact the challenge '
         f'organizers. The following information may help them:\n'
         f'User: {job.submission.creator.username}\n'
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
@@ -72,7 +72,7 @@
     form_class = SubmissionForm
     success_message = (
         "Your submission was successful. "
-        "Please keep checking this page for your result."
+        "You will receive an email when your result is ready."
     )
 
     def get_form_kwargs(self):

{"golden_diff": "diff --git a/app/grandchallenge/evaluation/emails.py b/app/grandchallenge/evaluation/emails.py\n--- a/app/grandchallenge/evaluation/emails.py\n+++ b/app/grandchallenge/evaluation/emails.py\n@@ -4,6 +4,7 @@\n from django.core.mail import send_mail\n \n from grandchallenge.core.urlresolvers import reverse\n+from grandchallenge.evaluation.templatetags.evaluation_extras import user_error\n \n \n def send_failed_job_email(job):\n@@ -11,7 +12,7 @@\n         f'Unfortunately the evaluation for the submission to '\n         f'{job.challenge.short_name} failed with an error. The error message '\n         f'is:\\n\\n'\n-        f'{job.output}\\n\\n'\n+        f'{user_error(job.output)}\\n\\n'\n         f'You may wish to try and correct this, or contact the challenge '\n         f'organizers. The following information may help them:\\n'\n         f'User: {job.submission.creator.username}\\n'\ndiff --git a/app/grandchallenge/evaluation/views.py b/app/grandchallenge/evaluation/views.py\n--- a/app/grandchallenge/evaluation/views.py\n+++ b/app/grandchallenge/evaluation/views.py\n@@ -72,7 +72,7 @@\n     form_class = SubmissionForm\n     success_message = (\n         \"Your submission was successful. \"\n-        \"Please keep checking this page for your result.\"\n+        \"You will receive an email when your result is ready.\"\n     )\n \n     def get_form_kwargs(self):\n", "issue": "Make error message emails less verbose\nThe entire stacktrace is included right now.\n", "before_files": [{"content": "import json\n\nfrom django.conf import settings\nfrom django.core.mail import send_mail\n\nfrom grandchallenge.core.urlresolvers import reverse\n\n\ndef send_failed_job_email(job):\n    message = (\n        f'Unfortunately the evaluation for the submission to '\n        f'{job.challenge.short_name} failed with an error. The error message '\n        f'is:\\n\\n'\n        f'{job.output}\\n\\n'\n        f'You may wish to try and correct this, or contact the challenge '\n        f'organizers. The following information may help them:\\n'\n        f'User: {job.submission.creator.username}\\n'\n        f'Job ID: {job.pk}\\n'\n        f'Submission ID: {job.submission.pk}'\n    )\n    recipient_emails = [o.email for o in job.challenge.get_admins()]\n    recipient_emails.append(job.submission.creator.email)\n    for email in recipient_emails:\n        send_mail(\n            subject='Evaluation Failed',\n            message=message,\n            from_email=settings.DEFAULT_FROM_EMAIL,\n            recipient_list=[email],\n        )\n\n\ndef send_new_result_email(result):\n    recipient_emails = [o.email for o in result.challenge.get_admins()]\n    message = (\n        f'There is a new result for {result.challenge.short_name} from '\n        f'{result.job.submission.creator.username}. The following metrics '\n        f'were calculated:\\n\\n'\n        f'{json.dumps(result.metrics, indent=2)}\\n\\n'\n    )\n    if result.public:\n        leaderboard_url = reverse(\n            'evaluation:result-list',\n            kwargs={'challenge_short_name': result.challenge.short_name},\n        )\n        message += (\n            f'You can view the result on the leaderboard here: '\n            f'{leaderboard_url}'\n        )\n        recipient_emails.append(result.job.submission.creator.email)\n    else:\n        message += (\n            f'You can publish the result on the leaderboard here: '\n            f'{result.get_absolute_url()}'\n        )\n    for email in recipient_emails:\n        send_mail(\n            subject=f'New Result for {result.challenge.short_name}',\n            message=message,\n            from_email=settings.DEFAULT_FROM_EMAIL,\n            recipient_list=[email],\n        )\n", "path": "app/grandchallenge/evaluation/emails.py"}, {"content": "from datetime import timedelta, datetime\nfrom typing import Dict\n\nfrom django.contrib.messages.views import SuccessMessageMixin\nfrom django.core.files import File\nfrom django.db.models import Q\nfrom django.utils import timezone\nfrom django.views.generic import (CreateView, ListView, DetailView, UpdateView)\n\nfrom grandchallenge.core.permissions.mixins import (\n    UserIsChallengeAdminMixin, UserIsChallengeParticipantOrAdminMixin\n)\nfrom grandchallenge.core.urlresolvers import reverse\nfrom grandchallenge.evaluation.forms import MethodForm, SubmissionForm\nfrom grandchallenge.evaluation.models import (\n    Result, Submission, Job, Method, Config,\n)\n\n\nclass ConfigUpdate(UserIsChallengeAdminMixin, SuccessMessageMixin, UpdateView):\n    model = Config\n    fields = (\n        'use_teams',\n        'daily_submission_limit',\n        'score_title',\n        'score_jsonpath',\n        'score_default_sort',\n        'extra_results_columns',\n        'allow_submission_comments',\n        'allow_supplementary_file',\n        'require_supplementary_file',\n        'supplementary_file_label',\n        'supplementary_file_help_text',\n        'show_supplementary_file_link',\n    )\n    success_message = \"Configuration successfully updated\"\n\n    def get_object(self, queryset=None):\n        challenge = self.request.challenge\n        return challenge.evaluation_config\n\n\nclass MethodCreate(UserIsChallengeAdminMixin, CreateView):\n    model = Method\n    form_class = MethodForm\n\n    def form_valid(self, form):\n        form.instance.creator = self.request.user\n        form.instance.challenge = self.request.challenge\n        uploaded_file = form.cleaned_data['chunked_upload'][0]\n        with uploaded_file.open() as f:\n            form.instance.image.save(uploaded_file.name, File(f))\n        return super(MethodCreate, self).form_valid(form)\n\n\nclass MethodList(UserIsChallengeAdminMixin, ListView):\n    model = Method\n\n    def get_queryset(self):\n        queryset = super(MethodList, self).get_queryset()\n        return queryset.filter(challenge=self.request.challenge)\n\n\nclass MethodDetail(UserIsChallengeAdminMixin, DetailView):\n    model = Method\n\n\nclass SubmissionCreate(\n    UserIsChallengeParticipantOrAdminMixin, SuccessMessageMixin, CreateView\n):\n    model = Submission\n    form_class = SubmissionForm\n    success_message = (\n        \"Your submission was successful. \"\n        \"Please keep checking this page for your result.\"\n    )\n\n    def get_form_kwargs(self):\n        kwargs = super(SubmissionCreate, self).get_form_kwargs()\n        config = Config.objects.get(challenge=self.request.challenge)\n        kwargs.update(\n            {\n                'display_comment_field': config.allow_submission_comments,\n                'allow_supplementary_file': config.allow_supplementary_file,\n                'require_supplementary_file': config.require_supplementary_file,\n                'supplementary_file_label': config.supplementary_file_label,\n                'supplementary_file_help_text': config.supplementary_file_help_text,\n            }\n        )\n        return kwargs\n\n    def get_context_data(self, **kwargs):\n        context = super(SubmissionCreate, self).get_context_data(**kwargs)\n        config = Config.objects.get(challenge=self.request.challenge)\n        context.update(\n            self.get_next_submission(max_subs=config.daily_submission_limit)\n        )\n        pending_jobs = Job.objects.filter(\n            challenge=self.request.challenge,\n            submission__creator=self.request.user,\n            status__in=(Job.PENDING, Job.STARTED),\n        ).count()\n        context.update({'pending_jobs': pending_jobs})\n        return context\n\n    def get_next_submission(\n        self,\n        *,\n        max_subs: int,\n        period: timedelta =timedelta(days=1),\n        now: datetime = None\n    ) -> Dict:\n        \"\"\"\n        Determines the number of submissions left for the user in a given time\n        period, and when they can next submit.\n\n        :return: A dictionary containing remaining_submissions (int) and\n        next_submission_at (datetime)\n        \"\"\"\n        if now is None:\n            now = timezone.now()\n        subs = Submission.objects.filter(\n            challenge=self.request.challenge,\n            creator=self.request.user,\n            created__gte=now - period,\n        ).order_by(\n            '-created'\n        )\n        try:\n            next_sub_at = subs[max_subs - 1].created + period\n        except (IndexError, AssertionError):\n            next_sub_at = now\n        return {\n            'remaining_submissions': max_subs - len(subs),\n            'next_submission_at': next_sub_at,\n        }\n\n    def form_valid(self, form):\n        form.instance.creator = self.request.user\n        form.instance.challenge = self.request.challenge\n        uploaded_file = form.cleaned_data['chunked_upload'][0]\n        with uploaded_file.open() as f:\n            form.instance.file.save(uploaded_file.name, File(f))\n        return super(SubmissionCreate, self).form_valid(form)\n\n    def get_success_url(self):\n        return reverse(\n            'evaluation:job-list',\n            kwargs={'challenge_short_name': self.object.challenge.short_name},\n        )\n\n\nclass SubmissionList(UserIsChallengeParticipantOrAdminMixin, ListView):\n    model = Submission\n\n    def get_queryset(self):\n        \"\"\" Admins see everything, participants just their submissions \"\"\"\n        queryset = super(SubmissionList, self).get_queryset()\n        challenge = self.request.challenge\n        if challenge.is_admin(self.request.user):\n            return queryset.filter(challenge=self.request.challenge)\n\n        else:\n            return queryset.filter(\n                Q(challenge=self.request.challenge),\n                Q(creator__pk=self.request.user.pk),\n            )\n\n\nclass SubmissionDetail(UserIsChallengeAdminMixin, DetailView):\n    # TODO - if participant: list only their submissions\n    model = Submission\n\n\nclass JobCreate(UserIsChallengeAdminMixin, CreateView):\n    model = Job\n    fields = '__all__'\n\n\nclass JobList(UserIsChallengeParticipantOrAdminMixin, ListView):\n    model = Job\n\n    def get_queryset(self):\n        \"\"\" Admins see everything, participants just their jobs \"\"\"\n        queryset = super(JobList, self).get_queryset()\n        queryset = queryset.select_related('result')\n        challenge = self.request.challenge\n        if challenge.is_admin(self.request.user):\n            return queryset.filter(challenge=self.request.challenge)\n\n        else:\n            return queryset.filter(\n                Q(challenge=self.request.challenge),\n                Q(submission__creator__pk=self.request.user.pk),\n            )\n\n\nclass JobDetail(UserIsChallengeAdminMixin, DetailView):\n    # TODO - if participant: list only their jobs\n    model = Job\n\n\nclass ResultList(ListView):\n    model = Result\n\n    def get_queryset(self):\n        queryset = super(ResultList, self).get_queryset()\n        queryset = queryset.select_related(\n            'job__submission__creator__user_profile'\n        )\n        return queryset.filter(\n            Q(challenge=self.request.challenge), Q(public=True)\n        )\n\n\nclass ResultDetail(DetailView):\n    model = Result\n\n\nclass ResultUpdate(UserIsChallengeAdminMixin, SuccessMessageMixin, UpdateView):\n    model = Result\n    fields = ('public',)\n    success_message = ('Result successfully updated.')\n", "path": "app/grandchallenge/evaluation/views.py"}], "after_files": [{"content": "import json\n\nfrom django.conf import settings\nfrom django.core.mail import send_mail\n\nfrom grandchallenge.core.urlresolvers import reverse\nfrom grandchallenge.evaluation.templatetags.evaluation_extras import user_error\n\n\ndef send_failed_job_email(job):\n    message = (\n        f'Unfortunately the evaluation for the submission to '\n        f'{job.challenge.short_name} failed with an error. The error message '\n        f'is:\\n\\n'\n        f'{user_error(job.output)}\\n\\n'\n        f'You may wish to try and correct this, or contact the challenge '\n        f'organizers. The following information may help them:\\n'\n        f'User: {job.submission.creator.username}\\n'\n        f'Job ID: {job.pk}\\n'\n        f'Submission ID: {job.submission.pk}'\n    )\n    recipient_emails = [o.email for o in job.challenge.get_admins()]\n    recipient_emails.append(job.submission.creator.email)\n    for email in recipient_emails:\n        send_mail(\n            subject='Evaluation Failed',\n            message=message,\n            from_email=settings.DEFAULT_FROM_EMAIL,\n            recipient_list=[email],\n        )\n\n\ndef send_new_result_email(result):\n    recipient_emails = [o.email for o in result.challenge.get_admins()]\n    message = (\n        f'There is a new result for {result.challenge.short_name} from '\n        f'{result.job.submission.creator.username}. The following metrics '\n        f'were calculated:\\n\\n'\n        f'{json.dumps(result.metrics, indent=2)}\\n\\n'\n    )\n    if result.public:\n        leaderboard_url = reverse(\n            'evaluation:result-list',\n            kwargs={'challenge_short_name': result.challenge.short_name},\n        )\n        message += (\n            f'You can view the result on the leaderboard here: '\n            f'{leaderboard_url}'\n        )\n        recipient_emails.append(result.job.submission.creator.email)\n    else:\n        message += (\n            f'You can publish the result on the leaderboard here: '\n            f'{result.get_absolute_url()}'\n        )\n    for email in recipient_emails:\n        send_mail(\n            subject=f'New Result for {result.challenge.short_name}',\n            message=message,\n            from_email=settings.DEFAULT_FROM_EMAIL,\n            recipient_list=[email],\n        )\n", "path": "app/grandchallenge/evaluation/emails.py"}, {"content": "from datetime import timedelta, datetime\nfrom typing import Dict\n\nfrom django.contrib.messages.views import SuccessMessageMixin\nfrom django.core.files import File\nfrom django.db.models import Q\nfrom django.utils import timezone\nfrom django.views.generic import (CreateView, ListView, DetailView, UpdateView)\n\nfrom grandchallenge.core.permissions.mixins import (\n    UserIsChallengeAdminMixin, UserIsChallengeParticipantOrAdminMixin\n)\nfrom grandchallenge.core.urlresolvers import reverse\nfrom grandchallenge.evaluation.forms import MethodForm, SubmissionForm\nfrom grandchallenge.evaluation.models import (\n    Result, Submission, Job, Method, Config,\n)\n\n\nclass ConfigUpdate(UserIsChallengeAdminMixin, SuccessMessageMixin, UpdateView):\n    model = Config\n    fields = (\n        'use_teams',\n        'daily_submission_limit',\n        'score_title',\n        'score_jsonpath',\n        'score_default_sort',\n        'extra_results_columns',\n        'allow_submission_comments',\n        'allow_supplementary_file',\n        'require_supplementary_file',\n        'supplementary_file_label',\n        'supplementary_file_help_text',\n        'show_supplementary_file_link',\n    )\n    success_message = \"Configuration successfully updated\"\n\n    def get_object(self, queryset=None):\n        challenge = self.request.challenge\n        return challenge.evaluation_config\n\n\nclass MethodCreate(UserIsChallengeAdminMixin, CreateView):\n    model = Method\n    form_class = MethodForm\n\n    def form_valid(self, form):\n        form.instance.creator = self.request.user\n        form.instance.challenge = self.request.challenge\n        uploaded_file = form.cleaned_data['chunked_upload'][0]\n        with uploaded_file.open() as f:\n            form.instance.image.save(uploaded_file.name, File(f))\n        return super(MethodCreate, self).form_valid(form)\n\n\nclass MethodList(UserIsChallengeAdminMixin, ListView):\n    model = Method\n\n    def get_queryset(self):\n        queryset = super(MethodList, self).get_queryset()\n        return queryset.filter(challenge=self.request.challenge)\n\n\nclass MethodDetail(UserIsChallengeAdminMixin, DetailView):\n    model = Method\n\n\nclass SubmissionCreate(\n    UserIsChallengeParticipantOrAdminMixin, SuccessMessageMixin, CreateView\n):\n    model = Submission\n    form_class = SubmissionForm\n    success_message = (\n        \"Your submission was successful. \"\n        \"You will receive an email when your result is ready.\"\n    )\n\n    def get_form_kwargs(self):\n        kwargs = super(SubmissionCreate, self).get_form_kwargs()\n        config = Config.objects.get(challenge=self.request.challenge)\n        kwargs.update(\n            {\n                'display_comment_field': config.allow_submission_comments,\n                'allow_supplementary_file': config.allow_supplementary_file,\n                'require_supplementary_file': config.require_supplementary_file,\n                'supplementary_file_label': config.supplementary_file_label,\n                'supplementary_file_help_text': config.supplementary_file_help_text,\n            }\n        )\n        return kwargs\n\n    def get_context_data(self, **kwargs):\n        context = super(SubmissionCreate, self).get_context_data(**kwargs)\n        config = Config.objects.get(challenge=self.request.challenge)\n        context.update(\n            self.get_next_submission(max_subs=config.daily_submission_limit)\n        )\n        pending_jobs = Job.objects.filter(\n            challenge=self.request.challenge,\n            submission__creator=self.request.user,\n            status__in=(Job.PENDING, Job.STARTED),\n        ).count()\n        context.update({'pending_jobs': pending_jobs})\n        return context\n\n    def get_next_submission(\n        self,\n        *,\n        max_subs: int,\n        period: timedelta =timedelta(days=1),\n        now: datetime = None\n    ) -> Dict:\n        \"\"\"\n        Determines the number of submissions left for the user in a given time\n        period, and when they can next submit.\n\n        :return: A dictionary containing remaining_submissions (int) and\n        next_submission_at (datetime)\n        \"\"\"\n        if now is None:\n            now = timezone.now()\n        subs = Submission.objects.filter(\n            challenge=self.request.challenge,\n            creator=self.request.user,\n            created__gte=now - period,\n        ).order_by(\n            '-created'\n        )\n        try:\n            next_sub_at = subs[max_subs - 1].created + period\n        except (IndexError, AssertionError):\n            next_sub_at = now\n        return {\n            'remaining_submissions': max_subs - len(subs),\n            'next_submission_at': next_sub_at,\n        }\n\n    def form_valid(self, form):\n        form.instance.creator = self.request.user\n        form.instance.challenge = self.request.challenge\n        uploaded_file = form.cleaned_data['chunked_upload'][0]\n        with uploaded_file.open() as f:\n            form.instance.file.save(uploaded_file.name, File(f))\n        return super(SubmissionCreate, self).form_valid(form)\n\n    def get_success_url(self):\n        return reverse(\n            'evaluation:job-list',\n            kwargs={'challenge_short_name': self.object.challenge.short_name},\n        )\n\n\nclass SubmissionList(UserIsChallengeParticipantOrAdminMixin, ListView):\n    model = Submission\n\n    def get_queryset(self):\n        \"\"\" Admins see everything, participants just their submissions \"\"\"\n        queryset = super(SubmissionList, self).get_queryset()\n        challenge = self.request.challenge\n        if challenge.is_admin(self.request.user):\n            return queryset.filter(challenge=self.request.challenge)\n\n        else:\n            return queryset.filter(\n                Q(challenge=self.request.challenge),\n                Q(creator__pk=self.request.user.pk),\n            )\n\n\nclass SubmissionDetail(UserIsChallengeAdminMixin, DetailView):\n    # TODO - if participant: list only their submissions\n    model = Submission\n\n\nclass JobCreate(UserIsChallengeAdminMixin, CreateView):\n    model = Job\n    fields = '__all__'\n\n\nclass JobList(UserIsChallengeParticipantOrAdminMixin, ListView):\n    model = Job\n\n    def get_queryset(self):\n        \"\"\" Admins see everything, participants just their jobs \"\"\"\n        queryset = super(JobList, self).get_queryset()\n        queryset = queryset.select_related('result')\n        challenge = self.request.challenge\n        if challenge.is_admin(self.request.user):\n            return queryset.filter(challenge=self.request.challenge)\n\n        else:\n            return queryset.filter(\n                Q(challenge=self.request.challenge),\n                Q(submission__creator__pk=self.request.user.pk),\n            )\n\n\nclass JobDetail(UserIsChallengeAdminMixin, DetailView):\n    # TODO - if participant: list only their jobs\n    model = Job\n\n\nclass ResultList(ListView):\n    model = Result\n\n    def get_queryset(self):\n        queryset = super(ResultList, self).get_queryset()\n        queryset = queryset.select_related(\n            'job__submission__creator__user_profile'\n        )\n        return queryset.filter(\n            Q(challenge=self.request.challenge), Q(public=True)\n        )\n\n\nclass ResultDetail(DetailView):\n    model = Result\n\n\nclass ResultUpdate(UserIsChallengeAdminMixin, SuccessMessageMixin, UpdateView):\n    model = Result\n    fields = ('public',)\n    success_message = ('Result successfully updated.')\n", "path": "app/grandchallenge/evaluation/views.py"}]}