We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
"Run algorithm" page is missing the breadcrumb bar
Maybe this is on purpose, but I find it confusing that the page that allows users to upload images and run an algorithm does not show the breadcrumb bar to get back to the algorithm overview page.

Example: https://grand-challenge.org/algorithms/vertebra-segmentation/run/

To be consistent with the job list the title of that page could also read "Run this algorithm" instead "Run an algorithm"

--- 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/reader_studies/views.py`
Content:
```
1 import csv
2 import re
3 
4 from dal import autocomplete
5 from django.conf import settings
6 from django.contrib import messages
7 from django.contrib.auth import get_user_model
8 from django.contrib.auth.mixins import (
9     PermissionRequiredMixin,
10     UserPassesTestMixin,
11 )
12 from django.contrib.messages.views import SuccessMessageMixin
13 from django.http import Http404, HttpResponse
14 from django.views.generic import (
15     CreateView,
16     DetailView,
17     FormView,
18     ListView,
19     UpdateView,
20 )
21 from guardian.mixins import (
22     LoginRequiredMixin,
23     PermissionListMixin,
24     PermissionRequiredMixin as ObjectPermissionRequiredMixin,
25 )
26 from guardian.shortcuts import get_perms
27 from rest_framework.decorators import action
28 from rest_framework.mixins import (
29     CreateModelMixin,
30     ListModelMixin,
31     RetrieveModelMixin,
32 )
33 from rest_framework.permissions import DjangoObjectPermissions
34 from rest_framework.response import Response
35 from rest_framework.viewsets import (
36     GenericViewSet,
37     ReadOnlyModelViewSet,
38 )
39 from rest_framework_guardian.filters import ObjectPermissionsFilter
40 
41 from grandchallenge.cases.forms import UploadRawImagesForm
42 from grandchallenge.cases.models import RawImageUploadSession
43 from grandchallenge.core.permissions.rest_framework import (
44     DjangoObjectOnlyPermissions,
45 )
46 from grandchallenge.reader_studies.forms import (
47     EditorsForm,
48     QuestionForm,
49     ReaderStudyCreateForm,
50     ReaderStudyUpdateForm,
51     ReadersForm,
52 )
53 from grandchallenge.reader_studies.models import Answer, Question, ReaderStudy
54 from grandchallenge.reader_studies.serializers import (
55     AnswerSerializer,
56     QuestionSerializer,
57     ReaderStudySerializer,
58 )
59 
60 
61 class ReaderStudyList(LoginRequiredMixin, PermissionListMixin, ListView):
62     model = ReaderStudy
63     permission_required = (
64         f"{ReaderStudy._meta.app_label}.view_{ReaderStudy._meta.model_name}"
65     )
66 
67 
68 class ReaderStudyCreate(
69     LoginRequiredMixin, PermissionRequiredMixin, CreateView
70 ):
71     model = ReaderStudy
72     form_class = ReaderStudyCreateForm
73     permission_required = (
74         f"{ReaderStudy._meta.app_label}.add_{ReaderStudy._meta.model_name}"
75     )
76 
77     def get_form_kwargs(self):
78         kwargs = super().get_form_kwargs()
79         kwargs.update({"user": self.request.user})
80         return kwargs
81 
82     def form_valid(self, form):
83         response = super().form_valid(form)
84         self.object.add_editor(self.request.user)
85         return response
86 
87 
88 class ReaderStudyDetail(
89     LoginRequiredMixin, ObjectPermissionRequiredMixin, DetailView
90 ):
91     model = ReaderStudy
92     permission_required = (
93         f"{ReaderStudy._meta.app_label}.view_{ReaderStudy._meta.model_name}"
94     )
95     raise_exception = True
96 
97     def get_context_data(self, **kwargs):
98         context = super().get_context_data(**kwargs)
99         change_perm = f"change_{ReaderStudy._meta.model_name}"
100         if change_perm in get_perms(self.request.user, self.object):
101             readers = [
102                 {
103                     "obj": reader,
104                     "progress": self.object.get_progress_for_user(reader),
105                 }
106                 for reader in self.object.readers_group.user_set.all()
107             ]
108             context.update({"readers": readers})
109         else:
110             user_progress = self.object.get_progress_for_user(
111                 self.request.user
112             )
113             context.update({"progress": user_progress})
114         context.update(
115             {"user_is_reader": self.object.is_reader(user=self.request.user)}
116         )
117         return context
118 
119 
120 class ReaderStudyUpdate(
121     LoginRequiredMixin, ObjectPermissionRequiredMixin, UpdateView
122 ):
123     model = ReaderStudy
124     form_class = ReaderStudyUpdateForm
125     permission_required = (
126         f"{ReaderStudy._meta.app_label}.change_{ReaderStudy._meta.model_name}"
127     )
128     raise_exception = True
129 
130     def get_form_kwargs(self):
131         kwargs = super().get_form_kwargs()
132         kwargs.update({"user": self.request.user})
133         return kwargs
134 
135 
136 class QuestionUpdate(
137     LoginRequiredMixin, ObjectPermissionRequiredMixin, UpdateView
138 ):
139     model = Question
140     form_class = QuestionForm
141     template_name = "reader_studies/readerstudy_update_object.html"
142     permission_required = (
143         f"{ReaderStudy._meta.app_label}.change_{ReaderStudy._meta.model_name}"
144     )
145     raise_exception = True
146 
147     def get_permission_object(self):
148         return self.reader_study
149 
150     @property
151     def reader_study(self):
152         return ReaderStudy.objects.get(slug=self.kwargs["slug"])
153 
154     def get_context_data(self, **kwargs):
155         context = super().get_context_data(**kwargs)
156         form_fields = context["form"].fields
157         for field_name in self.object.read_only_fields:
158             form_fields[field_name].required = False
159             form_fields[field_name].disabled = True
160 
161         return context
162 
163     def get_success_url(self):
164         return self.object.reader_study.get_absolute_url()
165 
166 
167 class AddObjectToReaderStudyMixin(
168     LoginRequiredMixin, ObjectPermissionRequiredMixin, CreateView
169 ):
170     """
171     Mixin that adds an object that has a foreign key to a reader study and a
172     creator. The url to this view must include a slug that points to the slug
173     of the reader study.
174 
175     Must be placed to the left of ObjectPermissionRequiredMixin.
176     """
177 
178     permission_required = (
179         f"{ReaderStudy._meta.app_label}.change_{ReaderStudy._meta.model_name}"
180     )
181     raise_exception = True
182 
183     def get_permission_object(self):
184         return self.reader_study
185 
186     @property
187     def reader_study(self):
188         return ReaderStudy.objects.get(slug=self.kwargs["slug"])
189 
190     def get_context_data(self, **kwargs):
191         context = super().get_context_data(**kwargs)
192         context.update(
193             {"object": self.reader_study, "type_to_add": self.type_to_add}
194         )
195         return context
196 
197     def form_valid(self, form):
198         form.instance.creator = self.request.user
199         form.instance.reader_study = self.reader_study
200         return super().form_valid(form)
201 
202     def get_success_url(self):
203         return self.object.reader_study.get_absolute_url()
204 
205 
206 class AddImagesToReaderStudy(AddObjectToReaderStudyMixin):
207     model = RawImageUploadSession
208     form_class = UploadRawImagesForm
209     template_name = "reader_studies/readerstudy_add_object.html"
210     type_to_add = "images"
211 
212     def get_form_kwargs(self):
213         kwargs = super().get_form_kwargs()
214         kwargs.update({"user": self.request.user})
215         return kwargs
216 
217 
218 class AddQuestionToReaderStudy(AddObjectToReaderStudyMixin):
219     model = Question
220     form_class = QuestionForm
221     template_name = "reader_studies/readerstudy_add_object.html"
222     type_to_add = "question"
223 
224 
225 class ReaderStudyUserAutocomplete(
226     LoginRequiredMixin, UserPassesTestMixin, autocomplete.Select2QuerySetView
227 ):
228     def test_func(self):
229         group_pks = (
230             ReaderStudy.objects.all()
231             .select_related("editors_group")
232             .values_list("editors_group__pk", flat=True)
233         )
234         return (
235             self.request.user.is_superuser
236             or self.request.user.groups.filter(pk__in=group_pks).exists()
237         )
238 
239     def get_queryset(self):
240         qs = (
241             get_user_model()
242             .objects.all()
243             .order_by("username")
244             .exclude(username=settings.ANONYMOUS_USER_NAME)
245         )
246 
247         if self.q:
248             qs = qs.filter(username__istartswith=self.q)
249 
250         return qs
251 
252 
253 class ReaderStudyUserGroupUpdateMixin(
254     LoginRequiredMixin,
255     ObjectPermissionRequiredMixin,
256     SuccessMessageMixin,
257     FormView,
258 ):
259     template_name = "reader_studies/readerstudy_user_groups_form.html"
260     permission_required = (
261         f"{ReaderStudy._meta.app_label}.change_{ReaderStudy._meta.model_name}"
262     )
263     raise_exception = True
264 
265     def get_permission_object(self):
266         return self.reader_study
267 
268     @property
269     def reader_study(self):
270         return ReaderStudy.objects.get(slug=self.kwargs["slug"])
271 
272     def get_context_data(self, **kwargs):
273         context = super().get_context_data(**kwargs)
274         context.update(
275             {"object": self.reader_study, "role": self.get_form().role}
276         )
277         return context
278 
279     def get_success_url(self):
280         return self.reader_study.get_absolute_url()
281 
282     def form_valid(self, form):
283         form.add_or_remove_user(reader_study=self.reader_study)
284         return super().form_valid(form)
285 
286 
287 class EditorsUpdate(ReaderStudyUserGroupUpdateMixin):
288     form_class = EditorsForm
289     success_message = "Editors successfully updated"
290 
291 
292 class ReadersUpdate(ReaderStudyUserGroupUpdateMixin):
293     form_class = ReadersForm
294     success_message = "Readers successfully updated"
295 
296 
297 class ExportCSVMixin(object):
298     def _create_dicts(self, headers, data):
299         return map(lambda x: dict(zip(headers, x)), data)
300 
301     def _preprocess_data(self, data):
302         processed = []
303         for entry in data:
304             processed.append(
305                 map(lambda x: re.sub(r"[\n\r\t]", " ", str(x)), entry)
306             )
307         return processed
308 
309     def _create_csv_response(self, data, headers, filename="export.csv"):
310         response = HttpResponse(content_type="text/csv")
311         response["Content-Disposition"] = f'attachment; filename="{filename}"'
312         writer = csv.DictWriter(
313             response,
314             quoting=csv.QUOTE_ALL,
315             escapechar="\\",
316             fieldnames=headers,
317         )
318         writer.writeheader()
319         csv_dict = self._create_dicts(headers, self._preprocess_data(data))
320         writer.writerows(csv_dict)
321 
322         return response
323 
324 
325 class ReaderStudyViewSet(ExportCSVMixin, ReadOnlyModelViewSet):
326     serializer_class = ReaderStudySerializer
327     queryset = ReaderStudy.objects.all().prefetch_related(
328         "images", "questions"
329     )
330     permission_classes = [DjangoObjectOnlyPermissions]
331     filter_backends = [ObjectPermissionsFilter]
332     change_permission = (
333         f"{ReaderStudy._meta.app_label}.change_{ReaderStudy._meta.model_name}"
334     )
335 
336     def _check_change_perms(self, user, obj):
337         if not (user and user.has_perm(self.change_permission, obj)):
338             raise Http404()
339 
340     @action(detail=True)
341     def export_answers(self, request, pk=None):
342         reader_study = self.get_object()
343         self._check_change_perms(request.user, reader_study)
344 
345         data = [
346             answer.csv_values
347             for answer in Answer.objects.select_related(
348                 "question__reader_study"
349             )
350             .select_related("creator")
351             .prefetch_related("images")
352             .filter(question__reader_study=reader_study)
353         ]
354 
355         return self._create_csv_response(
356             data,
357             Answer.csv_headers,
358             filename=f"{reader_study.slug}-answers.csv",
359         )
360 
361     @action(detail=True, methods=["patch"])
362     def generate_hanging_list(self, request, pk=None):
363         reader_study = self.get_object()
364         reader_study.generate_hanging_list()
365         messages.add_message(
366             request, messages.SUCCESS, "Hanging list re-generated."
367         )
368         return Response({"status": "Hanging list generated."},)
369 
370 
371 class QuestionViewSet(ReadOnlyModelViewSet):
372     serializer_class = QuestionSerializer
373     queryset = Question.objects.all().select_related("reader_study")
374     permission_classes = [DjangoObjectPermissions]
375     filter_backends = [ObjectPermissionsFilter]
376 
377 
378 class AnswerViewSet(
379     CreateModelMixin, RetrieveModelMixin, ListModelMixin, GenericViewSet
380 ):
381     serializer_class = AnswerSerializer
382     queryset = (
383         Answer.objects.all()
384         .select_related("creator")
385         .prefetch_related("images")
386     )
387     permission_classes = [DjangoObjectPermissions]
388     filter_backends = [ObjectPermissionsFilter]
389 
390     def perform_create(self, serializer):
391         serializer.save(creator=self.request.user)
392 
393     @action(detail=False)
394     def mine(self, request):
395         """
396         An endpoint that returns the questions that have been answered by
397         the current user.
398         """
399         queryset = self.filter_queryset(
400             self.get_queryset().filter(creator=request.user)
401         )
402 
403         page = self.paginate_queryset(queryset)
404         if page is not None:
405             serializer = self.get_serializer(page, many=True)
406             return self.get_paginated_response(serializer.data)
407 
408         serializer = self.get_serializer(queryset, many=True)
409         return Response(serializer.data)
410
```
--- 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/reader_studies/views.py b/app/grandchallenge/reader_studies/views.py
--- a/app/grandchallenge/reader_studies/views.py
+++ b/app/grandchallenge/reader_studies/views.py
@@ -157,7 +157,7 @@
         for field_name in self.object.read_only_fields:
             form_fields[field_name].required = False
             form_fields[field_name].disabled = True
-
+        context.update({"reader_study": self.reader_study})
         return context
 
     def get_success_url(self):

{"golden_diff": "diff --git a/app/grandchallenge/reader_studies/views.py b/app/grandchallenge/reader_studies/views.py\n--- a/app/grandchallenge/reader_studies/views.py\n+++ b/app/grandchallenge/reader_studies/views.py\n@@ -157,7 +157,7 @@\n         for field_name in self.object.read_only_fields:\n             form_fields[field_name].required = False\n             form_fields[field_name].disabled = True\n-\n+        context.update({\"reader_study\": self.reader_study})\n         return context\n \n     def get_success_url(self):\n", "issue": "\"Run algorithm\" page is missing the breadcrumb bar\nMaybe this is on purpose, but I find it confusing that the page that allows users to upload images and run an algorithm does not show the breadcrumb bar to get back to the algorithm overview page.\r\n\r\nExample: https://grand-challenge.org/algorithms/vertebra-segmentation/run/\r\n\r\nTo be consistent with the job list the title of that page could also read \"Run this algorithm\" instead \"Run an algorithm\"\n", "before_files": [{"content": "import csv\nimport re\n\nfrom dal import autocomplete\nfrom django.conf import settings\nfrom django.contrib import messages\nfrom django.contrib.auth import get_user_model\nfrom django.contrib.auth.mixins import (\n    PermissionRequiredMixin,\n    UserPassesTestMixin,\n)\nfrom django.contrib.messages.views import SuccessMessageMixin\nfrom django.http import Http404, HttpResponse\nfrom django.views.generic import (\n    CreateView,\n    DetailView,\n    FormView,\n    ListView,\n    UpdateView,\n)\nfrom guardian.mixins import (\n    LoginRequiredMixin,\n    PermissionListMixin,\n    PermissionRequiredMixin as ObjectPermissionRequiredMixin,\n)\nfrom guardian.shortcuts import get_perms\nfrom rest_framework.decorators import action\nfrom rest_framework.mixins import (\n    CreateModelMixin,\n    ListModelMixin,\n    RetrieveModelMixin,\n)\nfrom rest_framework.permissions import DjangoObjectPermissions\nfrom rest_framework.response import Response\nfrom rest_framework.viewsets import (\n    GenericViewSet,\n    ReadOnlyModelViewSet,\n)\nfrom rest_framework_guardian.filters import ObjectPermissionsFilter\n\nfrom grandchallenge.cases.forms import UploadRawImagesForm\nfrom grandchallenge.cases.models import RawImageUploadSession\nfrom grandchallenge.core.permissions.rest_framework import (\n    DjangoObjectOnlyPermissions,\n)\nfrom grandchallenge.reader_studies.forms import (\n    EditorsForm,\n    QuestionForm,\n    ReaderStudyCreateForm,\n    ReaderStudyUpdateForm,\n    ReadersForm,\n)\nfrom grandchallenge.reader_studies.models import Answer, Question, ReaderStudy\nfrom grandchallenge.reader_studies.serializers import (\n    AnswerSerializer,\n    QuestionSerializer,\n    ReaderStudySerializer,\n)\n\n\nclass ReaderStudyList(LoginRequiredMixin, PermissionListMixin, ListView):\n    model = ReaderStudy\n    permission_required = (\n        f\"{ReaderStudy._meta.app_label}.view_{ReaderStudy._meta.model_name}\"\n    )\n\n\nclass ReaderStudyCreate(\n    LoginRequiredMixin, PermissionRequiredMixin, CreateView\n):\n    model = ReaderStudy\n    form_class = ReaderStudyCreateForm\n    permission_required = (\n        f\"{ReaderStudy._meta.app_label}.add_{ReaderStudy._meta.model_name}\"\n    )\n\n    def get_form_kwargs(self):\n        kwargs = super().get_form_kwargs()\n        kwargs.update({\"user\": self.request.user})\n        return kwargs\n\n    def form_valid(self, form):\n        response = super().form_valid(form)\n        self.object.add_editor(self.request.user)\n        return response\n\n\nclass ReaderStudyDetail(\n    LoginRequiredMixin, ObjectPermissionRequiredMixin, DetailView\n):\n    model = ReaderStudy\n    permission_required = (\n        f\"{ReaderStudy._meta.app_label}.view_{ReaderStudy._meta.model_name}\"\n    )\n    raise_exception = True\n\n    def get_context_data(self, **kwargs):\n        context = super().get_context_data(**kwargs)\n        change_perm = f\"change_{ReaderStudy._meta.model_name}\"\n        if change_perm in get_perms(self.request.user, self.object):\n            readers = [\n                {\n                    \"obj\": reader,\n                    \"progress\": self.object.get_progress_for_user(reader),\n                }\n                for reader in self.object.readers_group.user_set.all()\n            ]\n            context.update({\"readers\": readers})\n        else:\n            user_progress = self.object.get_progress_for_user(\n                self.request.user\n            )\n            context.update({\"progress\": user_progress})\n        context.update(\n            {\"user_is_reader\": self.object.is_reader(user=self.request.user)}\n        )\n        return context\n\n\nclass ReaderStudyUpdate(\n    LoginRequiredMixin, ObjectPermissionRequiredMixin, UpdateView\n):\n    model = ReaderStudy\n    form_class = ReaderStudyUpdateForm\n    permission_required = (\n        f\"{ReaderStudy._meta.app_label}.change_{ReaderStudy._meta.model_name}\"\n    )\n    raise_exception = True\n\n    def get_form_kwargs(self):\n        kwargs = super().get_form_kwargs()\n        kwargs.update({\"user\": self.request.user})\n        return kwargs\n\n\nclass QuestionUpdate(\n    LoginRequiredMixin, ObjectPermissionRequiredMixin, UpdateView\n):\n    model = Question\n    form_class = QuestionForm\n    template_name = \"reader_studies/readerstudy_update_object.html\"\n    permission_required = (\n        f\"{ReaderStudy._meta.app_label}.change_{ReaderStudy._meta.model_name}\"\n    )\n    raise_exception = True\n\n    def get_permission_object(self):\n        return self.reader_study\n\n    @property\n    def reader_study(self):\n        return ReaderStudy.objects.get(slug=self.kwargs[\"slug\"])\n\n    def get_context_data(self, **kwargs):\n        context = super().get_context_data(**kwargs)\n        form_fields = context[\"form\"].fields\n        for field_name in self.object.read_only_fields:\n            form_fields[field_name].required = False\n            form_fields[field_name].disabled = True\n\n        return context\n\n    def get_success_url(self):\n        return self.object.reader_study.get_absolute_url()\n\n\nclass AddObjectToReaderStudyMixin(\n    LoginRequiredMixin, ObjectPermissionRequiredMixin, CreateView\n):\n    \"\"\"\n    Mixin that adds an object that has a foreign key to a reader study and a\n    creator. The url to this view must include a slug that points to the slug\n    of the reader study.\n\n    Must be placed to the left of ObjectPermissionRequiredMixin.\n    \"\"\"\n\n    permission_required = (\n        f\"{ReaderStudy._meta.app_label}.change_{ReaderStudy._meta.model_name}\"\n    )\n    raise_exception = True\n\n    def get_permission_object(self):\n        return self.reader_study\n\n    @property\n    def reader_study(self):\n        return ReaderStudy.objects.get(slug=self.kwargs[\"slug\"])\n\n    def get_context_data(self, **kwargs):\n        context = super().get_context_data(**kwargs)\n        context.update(\n            {\"object\": self.reader_study, \"type_to_add\": self.type_to_add}\n        )\n        return context\n\n    def form_valid(self, form):\n        form.instance.creator = self.request.user\n        form.instance.reader_study = self.reader_study\n        return super().form_valid(form)\n\n    def get_success_url(self):\n        return self.object.reader_study.get_absolute_url()\n\n\nclass AddImagesToReaderStudy(AddObjectToReaderStudyMixin):\n    model = RawImageUploadSession\n    form_class = UploadRawImagesForm\n    template_name = \"reader_studies/readerstudy_add_object.html\"\n    type_to_add = \"images\"\n\n    def get_form_kwargs(self):\n        kwargs = super().get_form_kwargs()\n        kwargs.update({\"user\": self.request.user})\n        return kwargs\n\n\nclass AddQuestionToReaderStudy(AddObjectToReaderStudyMixin):\n    model = Question\n    form_class = QuestionForm\n    template_name = \"reader_studies/readerstudy_add_object.html\"\n    type_to_add = \"question\"\n\n\nclass ReaderStudyUserAutocomplete(\n    LoginRequiredMixin, UserPassesTestMixin, autocomplete.Select2QuerySetView\n):\n    def test_func(self):\n        group_pks = (\n            ReaderStudy.objects.all()\n            .select_related(\"editors_group\")\n            .values_list(\"editors_group__pk\", flat=True)\n        )\n        return (\n            self.request.user.is_superuser\n            or self.request.user.groups.filter(pk__in=group_pks).exists()\n        )\n\n    def get_queryset(self):\n        qs = (\n            get_user_model()\n            .objects.all()\n            .order_by(\"username\")\n            .exclude(username=settings.ANONYMOUS_USER_NAME)\n        )\n\n        if self.q:\n            qs = qs.filter(username__istartswith=self.q)\n\n        return qs\n\n\nclass ReaderStudyUserGroupUpdateMixin(\n    LoginRequiredMixin,\n    ObjectPermissionRequiredMixin,\n    SuccessMessageMixin,\n    FormView,\n):\n    template_name = \"reader_studies/readerstudy_user_groups_form.html\"\n    permission_required = (\n        f\"{ReaderStudy._meta.app_label}.change_{ReaderStudy._meta.model_name}\"\n    )\n    raise_exception = True\n\n    def get_permission_object(self):\n        return self.reader_study\n\n    @property\n    def reader_study(self):\n        return ReaderStudy.objects.get(slug=self.kwargs[\"slug\"])\n\n    def get_context_data(self, **kwargs):\n        context = super().get_context_data(**kwargs)\n        context.update(\n            {\"object\": self.reader_study, \"role\": self.get_form().role}\n        )\n        return context\n\n    def get_success_url(self):\n        return self.reader_study.get_absolute_url()\n\n    def form_valid(self, form):\n        form.add_or_remove_user(reader_study=self.reader_study)\n        return super().form_valid(form)\n\n\nclass EditorsUpdate(ReaderStudyUserGroupUpdateMixin):\n    form_class = EditorsForm\n    success_message = \"Editors successfully updated\"\n\n\nclass ReadersUpdate(ReaderStudyUserGroupUpdateMixin):\n    form_class = ReadersForm\n    success_message = \"Readers successfully updated\"\n\n\nclass ExportCSVMixin(object):\n    def _create_dicts(self, headers, data):\n        return map(lambda x: dict(zip(headers, x)), data)\n\n    def _preprocess_data(self, data):\n        processed = []\n        for entry in data:\n            processed.append(\n                map(lambda x: re.sub(r\"[\\n\\r\\t]\", \" \", str(x)), entry)\n            )\n        return processed\n\n    def _create_csv_response(self, data, headers, filename=\"export.csv\"):\n        response = HttpResponse(content_type=\"text/csv\")\n        response[\"Content-Disposition\"] = f'attachment; filename=\"{filename}\"'\n        writer = csv.DictWriter(\n            response,\n            quoting=csv.QUOTE_ALL,\n            escapechar=\"\\\\\",\n            fieldnames=headers,\n        )\n        writer.writeheader()\n        csv_dict = self._create_dicts(headers, self._preprocess_data(data))\n        writer.writerows(csv_dict)\n\n        return response\n\n\nclass ReaderStudyViewSet(ExportCSVMixin, ReadOnlyModelViewSet):\n    serializer_class = ReaderStudySerializer\n    queryset = ReaderStudy.objects.all().prefetch_related(\n        \"images\", \"questions\"\n    )\n    permission_classes = [DjangoObjectOnlyPermissions]\n    filter_backends = [ObjectPermissionsFilter]\n    change_permission = (\n        f\"{ReaderStudy._meta.app_label}.change_{ReaderStudy._meta.model_name}\"\n    )\n\n    def _check_change_perms(self, user, obj):\n        if not (user and user.has_perm(self.change_permission, obj)):\n            raise Http404()\n\n    @action(detail=True)\n    def export_answers(self, request, pk=None):\n        reader_study = self.get_object()\n        self._check_change_perms(request.user, reader_study)\n\n        data = [\n            answer.csv_values\n            for answer in Answer.objects.select_related(\n                \"question__reader_study\"\n            )\n            .select_related(\"creator\")\n            .prefetch_related(\"images\")\n            .filter(question__reader_study=reader_study)\n        ]\n\n        return self._create_csv_response(\n            data,\n            Answer.csv_headers,\n            filename=f\"{reader_study.slug}-answers.csv\",\n        )\n\n    @action(detail=True, methods=[\"patch\"])\n    def generate_hanging_list(self, request, pk=None):\n        reader_study = self.get_object()\n        reader_study.generate_hanging_list()\n        messages.add_message(\n            request, messages.SUCCESS, \"Hanging list re-generated.\"\n        )\n        return Response({\"status\": \"Hanging list generated.\"},)\n\n\nclass QuestionViewSet(ReadOnlyModelViewSet):\n    serializer_class = QuestionSerializer\n    queryset = Question.objects.all().select_related(\"reader_study\")\n    permission_classes = [DjangoObjectPermissions]\n    filter_backends = [ObjectPermissionsFilter]\n\n\nclass AnswerViewSet(\n    CreateModelMixin, RetrieveModelMixin, ListModelMixin, GenericViewSet\n):\n    serializer_class = AnswerSerializer\n    queryset = (\n        Answer.objects.all()\n        .select_related(\"creator\")\n        .prefetch_related(\"images\")\n    )\n    permission_classes = [DjangoObjectPermissions]\n    filter_backends = [ObjectPermissionsFilter]\n\n    def perform_create(self, serializer):\n        serializer.save(creator=self.request.user)\n\n    @action(detail=False)\n    def mine(self, request):\n        \"\"\"\n        An endpoint that returns the questions that have been answered by\n        the current user.\n        \"\"\"\n        queryset = self.filter_queryset(\n            self.get_queryset().filter(creator=request.user)\n        )\n\n        page = self.paginate_queryset(queryset)\n        if page is not None:\n            serializer = self.get_serializer(page, many=True)\n            return self.get_paginated_response(serializer.data)\n\n        serializer = self.get_serializer(queryset, many=True)\n        return Response(serializer.data)\n", "path": "app/grandchallenge/reader_studies/views.py"}], "after_files": [{"content": "import csv\nimport re\n\nfrom dal import autocomplete\nfrom django.conf import settings\nfrom django.contrib import messages\nfrom django.contrib.auth import get_user_model\nfrom django.contrib.auth.mixins import (\n    PermissionRequiredMixin,\n    UserPassesTestMixin,\n)\nfrom django.contrib.messages.views import SuccessMessageMixin\nfrom django.http import Http404, HttpResponse\nfrom django.views.generic import (\n    CreateView,\n    DetailView,\n    FormView,\n    ListView,\n    UpdateView,\n)\nfrom guardian.mixins import (\n    LoginRequiredMixin,\n    PermissionListMixin,\n    PermissionRequiredMixin as ObjectPermissionRequiredMixin,\n)\nfrom guardian.shortcuts import get_perms\nfrom rest_framework.decorators import action\nfrom rest_framework.mixins import (\n    CreateModelMixin,\n    ListModelMixin,\n    RetrieveModelMixin,\n)\nfrom rest_framework.permissions import DjangoObjectPermissions\nfrom rest_framework.response import Response\nfrom rest_framework.viewsets import (\n    GenericViewSet,\n    ReadOnlyModelViewSet,\n)\nfrom rest_framework_guardian.filters import ObjectPermissionsFilter\n\nfrom grandchallenge.cases.forms import UploadRawImagesForm\nfrom grandchallenge.cases.models import RawImageUploadSession\nfrom grandchallenge.core.permissions.rest_framework import (\n    DjangoObjectOnlyPermissions,\n)\nfrom grandchallenge.reader_studies.forms import (\n    EditorsForm,\n    QuestionForm,\n    ReaderStudyCreateForm,\n    ReaderStudyUpdateForm,\n    ReadersForm,\n)\nfrom grandchallenge.reader_studies.models import Answer, Question, ReaderStudy\nfrom grandchallenge.reader_studies.serializers import (\n    AnswerSerializer,\n    QuestionSerializer,\n    ReaderStudySerializer,\n)\n\n\nclass ReaderStudyList(LoginRequiredMixin, PermissionListMixin, ListView):\n    model = ReaderStudy\n    permission_required = (\n        f\"{ReaderStudy._meta.app_label}.view_{ReaderStudy._meta.model_name}\"\n    )\n\n\nclass ReaderStudyCreate(\n    LoginRequiredMixin, PermissionRequiredMixin, CreateView\n):\n    model = ReaderStudy\n    form_class = ReaderStudyCreateForm\n    permission_required = (\n        f\"{ReaderStudy._meta.app_label}.add_{ReaderStudy._meta.model_name}\"\n    )\n\n    def get_form_kwargs(self):\n        kwargs = super().get_form_kwargs()\n        kwargs.update({\"user\": self.request.user})\n        return kwargs\n\n    def form_valid(self, form):\n        response = super().form_valid(form)\n        self.object.add_editor(self.request.user)\n        return response\n\n\nclass ReaderStudyDetail(\n    LoginRequiredMixin, ObjectPermissionRequiredMixin, DetailView\n):\n    model = ReaderStudy\n    permission_required = (\n        f\"{ReaderStudy._meta.app_label}.view_{ReaderStudy._meta.model_name}\"\n    )\n    raise_exception = True\n\n    def get_context_data(self, **kwargs):\n        context = super().get_context_data(**kwargs)\n        change_perm = f\"change_{ReaderStudy._meta.model_name}\"\n        if change_perm in get_perms(self.request.user, self.object):\n            readers = [\n                {\n                    \"obj\": reader,\n                    \"progress\": self.object.get_progress_for_user(reader),\n                }\n                for reader in self.object.readers_group.user_set.all()\n            ]\n            context.update({\"readers\": readers})\n        else:\n            user_progress = self.object.get_progress_for_user(\n                self.request.user\n            )\n            context.update({\"progress\": user_progress})\n        context.update(\n            {\"user_is_reader\": self.object.is_reader(user=self.request.user)}\n        )\n        return context\n\n\nclass ReaderStudyUpdate(\n    LoginRequiredMixin, ObjectPermissionRequiredMixin, UpdateView\n):\n    model = ReaderStudy\n    form_class = ReaderStudyUpdateForm\n    permission_required = (\n        f\"{ReaderStudy._meta.app_label}.change_{ReaderStudy._meta.model_name}\"\n    )\n    raise_exception = True\n\n    def get_form_kwargs(self):\n        kwargs = super().get_form_kwargs()\n        kwargs.update({\"user\": self.request.user})\n        return kwargs\n\n\nclass QuestionUpdate(\n    LoginRequiredMixin, ObjectPermissionRequiredMixin, UpdateView\n):\n    model = Question\n    form_class = QuestionForm\n    template_name = \"reader_studies/readerstudy_update_object.html\"\n    permission_required = (\n        f\"{ReaderStudy._meta.app_label}.change_{ReaderStudy._meta.model_name}\"\n    )\n    raise_exception = True\n\n    def get_permission_object(self):\n        return self.reader_study\n\n    @property\n    def reader_study(self):\n        return ReaderStudy.objects.get(slug=self.kwargs[\"slug\"])\n\n    def get_context_data(self, **kwargs):\n        context = super().get_context_data(**kwargs)\n        form_fields = context[\"form\"].fields\n        for field_name in self.object.read_only_fields:\n            form_fields[field_name].required = False\n            form_fields[field_name].disabled = True\n        context.update({\"reader_study\": self.reader_study})\n        return context\n\n    def get_success_url(self):\n        return self.object.reader_study.get_absolute_url()\n\n\nclass AddObjectToReaderStudyMixin(\n    LoginRequiredMixin, ObjectPermissionRequiredMixin, CreateView\n):\n    \"\"\"\n    Mixin that adds an object that has a foreign key to a reader study and a\n    creator. The url to this view must include a slug that points to the slug\n    of the reader study.\n\n    Must be placed to the left of ObjectPermissionRequiredMixin.\n    \"\"\"\n\n    permission_required = (\n        f\"{ReaderStudy._meta.app_label}.change_{ReaderStudy._meta.model_name}\"\n    )\n    raise_exception = True\n\n    def get_permission_object(self):\n        return self.reader_study\n\n    @property\n    def reader_study(self):\n        return ReaderStudy.objects.get(slug=self.kwargs[\"slug\"])\n\n    def get_context_data(self, **kwargs):\n        context = super().get_context_data(**kwargs)\n        context.update(\n            {\"object\": self.reader_study, \"type_to_add\": self.type_to_add}\n        )\n        return context\n\n    def form_valid(self, form):\n        form.instance.creator = self.request.user\n        form.instance.reader_study = self.reader_study\n        return super().form_valid(form)\n\n    def get_success_url(self):\n        return self.object.reader_study.get_absolute_url()\n\n\nclass AddImagesToReaderStudy(AddObjectToReaderStudyMixin):\n    model = RawImageUploadSession\n    form_class = UploadRawImagesForm\n    template_name = \"reader_studies/readerstudy_add_object.html\"\n    type_to_add = \"images\"\n\n    def get_form_kwargs(self):\n        kwargs = super().get_form_kwargs()\n        kwargs.update({\"user\": self.request.user})\n        return kwargs\n\n\nclass AddQuestionToReaderStudy(AddObjectToReaderStudyMixin):\n    model = Question\n    form_class = QuestionForm\n    template_name = \"reader_studies/readerstudy_add_object.html\"\n    type_to_add = \"question\"\n\n\nclass ReaderStudyUserAutocomplete(\n    LoginRequiredMixin, UserPassesTestMixin, autocomplete.Select2QuerySetView\n):\n    def test_func(self):\n        group_pks = (\n            ReaderStudy.objects.all()\n            .select_related(\"editors_group\")\n            .values_list(\"editors_group__pk\", flat=True)\n        )\n        return (\n            self.request.user.is_superuser\n            or self.request.user.groups.filter(pk__in=group_pks).exists()\n        )\n\n    def get_queryset(self):\n        qs = (\n            get_user_model()\n            .objects.all()\n            .order_by(\"username\")\n            .exclude(username=settings.ANONYMOUS_USER_NAME)\n        )\n\n        if self.q:\n            qs = qs.filter(username__istartswith=self.q)\n\n        return qs\n\n\nclass ReaderStudyUserGroupUpdateMixin(\n    LoginRequiredMixin,\n    ObjectPermissionRequiredMixin,\n    SuccessMessageMixin,\n    FormView,\n):\n    template_name = \"reader_studies/readerstudy_user_groups_form.html\"\n    permission_required = (\n        f\"{ReaderStudy._meta.app_label}.change_{ReaderStudy._meta.model_name}\"\n    )\n    raise_exception = True\n\n    def get_permission_object(self):\n        return self.reader_study\n\n    @property\n    def reader_study(self):\n        return ReaderStudy.objects.get(slug=self.kwargs[\"slug\"])\n\n    def get_context_data(self, **kwargs):\n        context = super().get_context_data(**kwargs)\n        context.update(\n            {\"object\": self.reader_study, \"role\": self.get_form().role}\n        )\n        return context\n\n    def get_success_url(self):\n        return self.reader_study.get_absolute_url()\n\n    def form_valid(self, form):\n        form.add_or_remove_user(reader_study=self.reader_study)\n        return super().form_valid(form)\n\n\nclass EditorsUpdate(ReaderStudyUserGroupUpdateMixin):\n    form_class = EditorsForm\n    success_message = \"Editors successfully updated\"\n\n\nclass ReadersUpdate(ReaderStudyUserGroupUpdateMixin):\n    form_class = ReadersForm\n    success_message = \"Readers successfully updated\"\n\n\nclass ExportCSVMixin(object):\n    def _create_dicts(self, headers, data):\n        return map(lambda x: dict(zip(headers, x)), data)\n\n    def _preprocess_data(self, data):\n        processed = []\n        for entry in data:\n            processed.append(\n                map(lambda x: re.sub(r\"[\\n\\r\\t]\", \" \", str(x)), entry)\n            )\n        return processed\n\n    def _create_csv_response(self, data, headers, filename=\"export.csv\"):\n        response = HttpResponse(content_type=\"text/csv\")\n        response[\"Content-Disposition\"] = f'attachment; filename=\"{filename}\"'\n        writer = csv.DictWriter(\n            response,\n            quoting=csv.QUOTE_ALL,\n            escapechar=\"\\\\\",\n            fieldnames=headers,\n        )\n        writer.writeheader()\n        csv_dict = self._create_dicts(headers, self._preprocess_data(data))\n        writer.writerows(csv_dict)\n\n        return response\n\n\nclass ReaderStudyViewSet(ExportCSVMixin, ReadOnlyModelViewSet):\n    serializer_class = ReaderStudySerializer\n    queryset = ReaderStudy.objects.all().prefetch_related(\n        \"images\", \"questions\"\n    )\n    permission_classes = [DjangoObjectOnlyPermissions]\n    filter_backends = [ObjectPermissionsFilter]\n    change_permission = (\n        f\"{ReaderStudy._meta.app_label}.change_{ReaderStudy._meta.model_name}\"\n    )\n\n    def _check_change_perms(self, user, obj):\n        if not (user and user.has_perm(self.change_permission, obj)):\n            raise Http404()\n\n    @action(detail=True)\n    def export_answers(self, request, pk=None):\n        reader_study = self.get_object()\n        self._check_change_perms(request.user, reader_study)\n\n        data = [\n            answer.csv_values\n            for answer in Answer.objects.select_related(\n                \"question__reader_study\"\n            )\n            .select_related(\"creator\")\n            .prefetch_related(\"images\")\n            .filter(question__reader_study=reader_study)\n        ]\n\n        return self._create_csv_response(\n            data,\n            Answer.csv_headers,\n            filename=f\"{reader_study.slug}-answers.csv\",\n        )\n\n    @action(detail=True, methods=[\"patch\"])\n    def generate_hanging_list(self, request, pk=None):\n        reader_study = self.get_object()\n        reader_study.generate_hanging_list()\n        messages.add_message(\n            request, messages.SUCCESS, \"Hanging list re-generated.\"\n        )\n        return Response({\"status\": \"Hanging list generated.\"},)\n\n\nclass QuestionViewSet(ReadOnlyModelViewSet):\n    serializer_class = QuestionSerializer\n    queryset = Question.objects.all().select_related(\"reader_study\")\n    permission_classes = [DjangoObjectPermissions]\n    filter_backends = [ObjectPermissionsFilter]\n\n\nclass AnswerViewSet(\n    CreateModelMixin, RetrieveModelMixin, ListModelMixin, GenericViewSet\n):\n    serializer_class = AnswerSerializer\n    queryset = (\n        Answer.objects.all()\n        .select_related(\"creator\")\n        .prefetch_related(\"images\")\n    )\n    permission_classes = [DjangoObjectPermissions]\n    filter_backends = [ObjectPermissionsFilter]\n\n    def perform_create(self, serializer):\n        serializer.save(creator=self.request.user)\n\n    @action(detail=False)\n    def mine(self, request):\n        \"\"\"\n        An endpoint that returns the questions that have been answered by\n        the current user.\n        \"\"\"\n        queryset = self.filter_queryset(\n            self.get_queryset().filter(creator=request.user)\n        )\n\n        page = self.paginate_queryset(queryset)\n        if page is not None:\n            serializer = self.get_serializer(page, many=True)\n            return self.get_paginated_response(serializer.data)\n\n        serializer = self.get_serializer(queryset, many=True)\n        return Response(serializer.data)\n", "path": "app/grandchallenge/reader_studies/views.py"}]}