We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
inconsistent 'difficult question' reports between learners

### Observed behavior

reported by @jtamiace during last week's bug bash

Clicking a “difficult question” in an exercise does not all show the same question for each learner. Number of learners for a question does not match what the overview says

Question 1 for Jamie and Laura are different:



| Page | Image |
|--|--|
| Question table | ![image](https://user-images.githubusercontent.com/2367265/52676270-7611b480-2ede-11e9-9fb5-7fa116873dde.png)| 
| Jamie's report |  ![image](https://user-images.githubusercontent.com/2367265/52676331-a0637200-2ede-11e9-927b-ec48c065ea05.png) |
| Laura's report | ![image](https://user-images.githubusercontent.com/2367265/52676314-9b062780-2ede-11e9-8926-3b048adc9eec.png)|




### Expected behavior

A 'difficult question' as reported should be consistent

### User-facing consequences

can't effectively use feature 


### Errors and logs
<!--
Relevant logs from:
 * the command line
 * ~/.kolibri/kolibri.log
 * the browser console

Please wrap errors in triple backticks for clean formatting like this:
```
01:10 info: something happened
01:12 error: something bad happened
```
-->

…

### Steps to reproduce
<!--
Precise steps that someone else can follow in order to see this behavior
-->

…

### Context
<!--
Tell us about your environment, including:
 * Kolibri version
 * Operating system
 * Browser
-->

…


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `kolibri/core/logger/api.py`
Content:
```
1 from django.core.exceptions import ObjectDoesNotExist
2 from django.db.models.query import F
3 from django.http import Http404
4 from django_filters import ModelChoiceFilter
5 from django_filters.rest_framework import CharFilter
6 from django_filters.rest_framework import DjangoFilterBackend
7 from django_filters.rest_framework import FilterSet
8 from rest_framework import filters
9 from rest_framework import viewsets
10 from rest_framework.response import Response
11 
12 from .models import AttemptLog
13 from .models import ContentSessionLog
14 from .models import ContentSummaryLog
15 from .models import ExamAttemptLog
16 from .models import ExamLog
17 from .models import MasteryLog
18 from .models import UserSessionLog
19 from .permissions import ExamActivePermissions
20 from .serializers import AttemptLogSerializer
21 from .serializers import ContentSessionLogSerializer
22 from .serializers import ContentSummaryLogSerializer
23 from .serializers import ExamAttemptLogSerializer
24 from .serializers import ExamLogSerializer
25 from .serializers import MasteryLogSerializer
26 from .serializers import TotalContentProgressSerializer
27 from .serializers import UserSessionLogSerializer
28 from kolibri.core.auth.api import KolibriAuthPermissions
29 from kolibri.core.auth.api import KolibriAuthPermissionsFilter
30 from kolibri.core.auth.filters import HierarchyRelationsFilter
31 from kolibri.core.auth.models import Classroom
32 from kolibri.core.auth.models import Collection
33 from kolibri.core.auth.models import Facility
34 from kolibri.core.auth.models import FacilityUser
35 from kolibri.core.auth.models import LearnerGroup
36 from kolibri.core.content.api import OptionalPageNumberPagination
37 from kolibri.core.exams.models import Exam
38 
39 
40 class BaseLogFilter(FilterSet):
41     facility = ModelChoiceFilter(method="filter_facility", queryset=Facility.objects.all())
42     classroom = ModelChoiceFilter(method="filter_classroom", queryset=Classroom.objects.all())
43     learner_group = ModelChoiceFilter(method="filter_learner_group", queryset=LearnerGroup.objects.all())
44 
45     # Only a superuser can filter by facilities
46     def filter_facility(self, queryset, name, value):
47         return queryset.filter(user__facility=value)
48 
49     def filter_classroom(self, queryset, name, value):
50         return HierarchyRelationsFilter(queryset).filter_by_hierarchy(
51             ancestor_collection=value,
52             target_user=F("user"),
53         )
54 
55     def filter_learner_group(self, queryset, name, value):
56         return HierarchyRelationsFilter(queryset).filter_by_hierarchy(
57             ancestor_collection=value,
58             target_user=F("user"),
59         )
60 
61 
62 class LoggerViewSet(viewsets.ModelViewSet):
63     def update(self, request, *args, **kwargs):
64         partial = kwargs.pop('partial', False)
65         model = self.queryset.model
66         lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
67         try:
68             instance = model.objects.get(id=self.kwargs[lookup_url_kwarg])
69             self.check_object_permissions(request, instance)
70         except (ValueError, ObjectDoesNotExist):
71             raise Http404
72         serializer = self.get_serializer(instance, data=request.data, partial=partial)
73         serializer.is_valid(raise_exception=True)
74         self.perform_update(serializer)
75 
76         if getattr(instance, '_prefetched_objects_cache', None):
77             # If 'prefetch_related' has been applied to a queryset, we need to
78             # forcibly invalidate the prefetch cache on the instance.
79             instance._prefetched_objects_cache = {}
80         default_response = dict(request.data)
81         # First look if the computed fields to be updated are listed:
82         updating_fields = getattr(serializer.root, 'update_fields', None)
83         # If not, fetch all the fields that are computed methods:
84         if updating_fields is None:
85             updating_fields = [field for field in serializer.fields if getattr(serializer.fields[field], 'method_name', None)]
86         for field in updating_fields:
87             method_name = getattr(serializer.fields[field], 'method_name', None)
88             if method_name:
89                 method = getattr(serializer.root, method_name)
90                 default_response[field] = method(instance)
91         return Response(default_response)
92 
93 
94 class ContentSessionLogFilter(BaseLogFilter):
95 
96     class Meta:
97         model = ContentSessionLog
98         fields = ['user_id', 'content_id']
99 
100 
101 class ContentSessionLogViewSet(LoggerViewSet):
102     permission_classes = (KolibriAuthPermissions,)
103     filter_backends = (KolibriAuthPermissionsFilter, DjangoFilterBackend)
104     queryset = ContentSessionLog.objects.all()
105     serializer_class = ContentSessionLogSerializer
106     pagination_class = OptionalPageNumberPagination
107     filter_class = ContentSessionLogFilter
108 
109 
110 class ContentSummaryLogFilter(BaseLogFilter):
111 
112     class Meta:
113         model = ContentSummaryLog
114         fields = ['user_id', 'content_id']
115 
116 
117 class ContentSummaryLogViewSet(LoggerViewSet):
118     permission_classes = (KolibriAuthPermissions,)
119     filter_backends = (KolibriAuthPermissionsFilter, DjangoFilterBackend)
120     queryset = ContentSummaryLog.objects.all()
121     serializer_class = ContentSummaryLogSerializer
122     pagination_class = OptionalPageNumberPagination
123     filter_class = ContentSummaryLogFilter
124 
125 
126 class TotalContentProgressViewSet(viewsets.ModelViewSet):
127     permission_classes = (KolibriAuthPermissions,)
128     filter_backends = (KolibriAuthPermissionsFilter,)
129     queryset = FacilityUser.objects.all()
130     serializer_class = TotalContentProgressSerializer
131 
132 
133 class UserSessionLogFilter(BaseLogFilter):
134 
135     class Meta:
136         model = UserSessionLog
137         fields = ['user_id']
138 
139 
140 class UserSessionLogViewSet(LoggerViewSet):
141     permission_classes = (KolibriAuthPermissions,)
142     filter_backends = (KolibriAuthPermissionsFilter, DjangoFilterBackend)
143     queryset = UserSessionLog.objects.all()
144     serializer_class = UserSessionLogSerializer
145     pagination_class = OptionalPageNumberPagination
146     filter_class = UserSessionLogFilter
147 
148 
149 class MasteryFilter(FilterSet):
150 
151     class Meta:
152         model = MasteryLog
153         fields = ['summarylog']
154 
155 
156 class MasteryLogViewSet(LoggerViewSet):
157     permission_classes = (KolibriAuthPermissions,)
158     filter_backends = (KolibriAuthPermissionsFilter, DjangoFilterBackend)
159     queryset = MasteryLog.objects.all()
160     serializer_class = MasteryLogSerializer
161     pagination_class = OptionalPageNumberPagination
162     filter_class = MasteryFilter
163 
164 
165 class AttemptFilter(BaseLogFilter):
166     content = CharFilter(method="filter_content")
167 
168     def filter_content(self, queryset, name, value):
169         return queryset.filter(masterylog__summarylog__content_id=value)
170 
171     class Meta:
172         model = AttemptLog
173         fields = ['masterylog', 'complete', 'user', 'content']
174 
175 
176 class AttemptLogViewSet(LoggerViewSet):
177     permission_classes = (KolibriAuthPermissions,)
178     filter_backends = (KolibriAuthPermissionsFilter, DjangoFilterBackend, filters.OrderingFilter)
179     queryset = AttemptLog.objects.all()
180     serializer_class = AttemptLogSerializer
181     pagination_class = OptionalPageNumberPagination
182     filter_class = AttemptFilter
183     ordering_fields = ('end_timestamp',)
184     ordering = ('end_timestamp',)
185 
186 
187 class ExamAttemptFilter(BaseLogFilter):
188     exam = ModelChoiceFilter(method="filter_exam", queryset=Exam.objects.all())
189     user = ModelChoiceFilter(method="filter_user", queryset=FacilityUser.objects.all())
190     content = CharFilter(field_name="content_id")
191 
192     def filter_exam(self, queryset, name, value):
193         return queryset.filter(examlog__exam=value)
194 
195     def filter_user(self, queryset, name, value):
196         return queryset.filter(examlog__user=value)
197 
198     class Meta:
199         model = ExamAttemptLog
200         fields = ['examlog', 'exam', 'user', 'content']
201 
202 
203 class ExamAttemptLogViewSet(LoggerViewSet):
204     permission_classes = (ExamActivePermissions, KolibriAuthPermissions, )
205     filter_backends = (KolibriAuthPermissionsFilter, DjangoFilterBackend, filters.OrderingFilter)
206     queryset = ExamAttemptLog.objects.all()
207     serializer_class = ExamAttemptLogSerializer
208     pagination_class = OptionalPageNumberPagination
209     filter_class = ExamAttemptFilter
210 
211 
212 class ExamLogFilter(BaseLogFilter):
213 
214     collection = ModelChoiceFilter(method="filter_collection", queryset=Collection.objects.all())
215 
216     def filter_collection(self, queryset, name, collection):
217         return HierarchyRelationsFilter(queryset).filter_by_hierarchy(
218             target_user=F('user'),
219             ancestor_collection=collection,
220         )
221 
222     class Meta:
223         model = ExamLog
224         fields = ['user', 'exam']
225 
226 
227 class ExamLogViewSet(viewsets.ModelViewSet):
228     permission_classes = (KolibriAuthPermissions,)
229     filter_backends = (KolibriAuthPermissionsFilter, DjangoFilterBackend)
230     queryset = ExamLog.objects.all()
231     serializer_class = ExamLogSerializer
232     pagination_class = OptionalPageNumberPagination
233     filter_class = ExamLogFilter
234
```
--- 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/kolibri/core/logger/api.py b/kolibri/core/logger/api.py
--- a/kolibri/core/logger/api.py
+++ b/kolibri/core/logger/api.py
@@ -170,7 +170,7 @@
 
     class Meta:
         model = AttemptLog
-        fields = ['masterylog', 'complete', 'user', 'content']
+        fields = ['masterylog', 'complete', 'user', 'content', 'item']
 
 
 class AttemptLogViewSet(LoggerViewSet):
@@ -197,7 +197,7 @@
 
     class Meta:
         model = ExamAttemptLog
-        fields = ['examlog', 'exam', 'user', 'content']
+        fields = ['examlog', 'exam', 'user', 'content', 'item']
 
 
 class ExamAttemptLogViewSet(LoggerViewSet):

{"golden_diff": "diff --git a/kolibri/core/logger/api.py b/kolibri/core/logger/api.py\n--- a/kolibri/core/logger/api.py\n+++ b/kolibri/core/logger/api.py\n@@ -170,7 +170,7 @@\n \n     class Meta:\n         model = AttemptLog\n-        fields = ['masterylog', 'complete', 'user', 'content']\n+        fields = ['masterylog', 'complete', 'user', 'content', 'item']\n \n \n class AttemptLogViewSet(LoggerViewSet):\n@@ -197,7 +197,7 @@\n \n     class Meta:\n         model = ExamAttemptLog\n-        fields = ['examlog', 'exam', 'user', 'content']\n+        fields = ['examlog', 'exam', 'user', 'content', 'item']\n \n \n class ExamAttemptLogViewSet(LoggerViewSet):\n", "issue": "inconsistent 'difficult question' reports between learners\n\r\n### Observed behavior\r\n\r\nreported by @jtamiace during last week's bug bash\r\n\r\nClicking a \u201cdifficult question\u201d in an exercise does not all show the same question for each learner. Number of learners for a question does not match what the overview says\r\n\r\nQuestion 1 for Jamie and Laura are different:\r\n\r\n\r\n\r\n| Page | Image |\r\n|--|--|\r\n| Question table | ![image](https://user-images.githubusercontent.com/2367265/52676270-7611b480-2ede-11e9-9fb5-7fa116873dde.png)| \r\n| Jamie's report |  ![image](https://user-images.githubusercontent.com/2367265/52676331-a0637200-2ede-11e9-927b-ec48c065ea05.png) |\r\n| Laura's report | ![image](https://user-images.githubusercontent.com/2367265/52676314-9b062780-2ede-11e9-8926-3b048adc9eec.png)|\r\n\r\n\r\n\r\n\r\n### Expected behavior\r\n\r\nA 'difficult question' as reported should be consistent\r\n\r\n### User-facing consequences\r\n\r\ncan't effectively use feature \r\n\r\n\r\n### Errors and logs\r\n<!--\r\nRelevant logs from:\r\n * the command line\r\n * ~/.kolibri/kolibri.log\r\n * the browser console\r\n\r\nPlease wrap errors in triple backticks for clean formatting like this:\r\n```\r\n01:10 info: something happened\r\n01:12 error: something bad happened\r\n```\r\n-->\r\n\r\n\u2026\r\n\r\n### Steps to reproduce\r\n<!--\r\nPrecise steps that someone else can follow in order to see this behavior\r\n-->\r\n\r\n\u2026\r\n\r\n### Context\r\n<!--\r\nTell us about your environment, including:\r\n * Kolibri version\r\n * Operating system\r\n * Browser\r\n-->\r\n\r\n\u2026\r\n\n", "before_files": [{"content": "from django.core.exceptions import ObjectDoesNotExist\nfrom django.db.models.query import F\nfrom django.http import Http404\nfrom django_filters import ModelChoiceFilter\nfrom django_filters.rest_framework import CharFilter\nfrom django_filters.rest_framework import DjangoFilterBackend\nfrom django_filters.rest_framework import FilterSet\nfrom rest_framework import filters\nfrom rest_framework import viewsets\nfrom rest_framework.response import Response\n\nfrom .models import AttemptLog\nfrom .models import ContentSessionLog\nfrom .models import ContentSummaryLog\nfrom .models import ExamAttemptLog\nfrom .models import ExamLog\nfrom .models import MasteryLog\nfrom .models import UserSessionLog\nfrom .permissions import ExamActivePermissions\nfrom .serializers import AttemptLogSerializer\nfrom .serializers import ContentSessionLogSerializer\nfrom .serializers import ContentSummaryLogSerializer\nfrom .serializers import ExamAttemptLogSerializer\nfrom .serializers import ExamLogSerializer\nfrom .serializers import MasteryLogSerializer\nfrom .serializers import TotalContentProgressSerializer\nfrom .serializers import UserSessionLogSerializer\nfrom kolibri.core.auth.api import KolibriAuthPermissions\nfrom kolibri.core.auth.api import KolibriAuthPermissionsFilter\nfrom kolibri.core.auth.filters import HierarchyRelationsFilter\nfrom kolibri.core.auth.models import Classroom\nfrom kolibri.core.auth.models import Collection\nfrom kolibri.core.auth.models import Facility\nfrom kolibri.core.auth.models import FacilityUser\nfrom kolibri.core.auth.models import LearnerGroup\nfrom kolibri.core.content.api import OptionalPageNumberPagination\nfrom kolibri.core.exams.models import Exam\n\n\nclass BaseLogFilter(FilterSet):\n    facility = ModelChoiceFilter(method=\"filter_facility\", queryset=Facility.objects.all())\n    classroom = ModelChoiceFilter(method=\"filter_classroom\", queryset=Classroom.objects.all())\n    learner_group = ModelChoiceFilter(method=\"filter_learner_group\", queryset=LearnerGroup.objects.all())\n\n    # Only a superuser can filter by facilities\n    def filter_facility(self, queryset, name, value):\n        return queryset.filter(user__facility=value)\n\n    def filter_classroom(self, queryset, name, value):\n        return HierarchyRelationsFilter(queryset).filter_by_hierarchy(\n            ancestor_collection=value,\n            target_user=F(\"user\"),\n        )\n\n    def filter_learner_group(self, queryset, name, value):\n        return HierarchyRelationsFilter(queryset).filter_by_hierarchy(\n            ancestor_collection=value,\n            target_user=F(\"user\"),\n        )\n\n\nclass LoggerViewSet(viewsets.ModelViewSet):\n    def update(self, request, *args, **kwargs):\n        partial = kwargs.pop('partial', False)\n        model = self.queryset.model\n        lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field\n        try:\n            instance = model.objects.get(id=self.kwargs[lookup_url_kwarg])\n            self.check_object_permissions(request, instance)\n        except (ValueError, ObjectDoesNotExist):\n            raise Http404\n        serializer = self.get_serializer(instance, data=request.data, partial=partial)\n        serializer.is_valid(raise_exception=True)\n        self.perform_update(serializer)\n\n        if getattr(instance, '_prefetched_objects_cache', None):\n            # If 'prefetch_related' has been applied to a queryset, we need to\n            # forcibly invalidate the prefetch cache on the instance.\n            instance._prefetched_objects_cache = {}\n        default_response = dict(request.data)\n        # First look if the computed fields to be updated are listed:\n        updating_fields = getattr(serializer.root, 'update_fields', None)\n        # If not, fetch all the fields that are computed methods:\n        if updating_fields is None:\n            updating_fields = [field for field in serializer.fields if getattr(serializer.fields[field], 'method_name', None)]\n        for field in updating_fields:\n            method_name = getattr(serializer.fields[field], 'method_name', None)\n            if method_name:\n                method = getattr(serializer.root, method_name)\n                default_response[field] = method(instance)\n        return Response(default_response)\n\n\nclass ContentSessionLogFilter(BaseLogFilter):\n\n    class Meta:\n        model = ContentSessionLog\n        fields = ['user_id', 'content_id']\n\n\nclass ContentSessionLogViewSet(LoggerViewSet):\n    permission_classes = (KolibriAuthPermissions,)\n    filter_backends = (KolibriAuthPermissionsFilter, DjangoFilterBackend)\n    queryset = ContentSessionLog.objects.all()\n    serializer_class = ContentSessionLogSerializer\n    pagination_class = OptionalPageNumberPagination\n    filter_class = ContentSessionLogFilter\n\n\nclass ContentSummaryLogFilter(BaseLogFilter):\n\n    class Meta:\n        model = ContentSummaryLog\n        fields = ['user_id', 'content_id']\n\n\nclass ContentSummaryLogViewSet(LoggerViewSet):\n    permission_classes = (KolibriAuthPermissions,)\n    filter_backends = (KolibriAuthPermissionsFilter, DjangoFilterBackend)\n    queryset = ContentSummaryLog.objects.all()\n    serializer_class = ContentSummaryLogSerializer\n    pagination_class = OptionalPageNumberPagination\n    filter_class = ContentSummaryLogFilter\n\n\nclass TotalContentProgressViewSet(viewsets.ModelViewSet):\n    permission_classes = (KolibriAuthPermissions,)\n    filter_backends = (KolibriAuthPermissionsFilter,)\n    queryset = FacilityUser.objects.all()\n    serializer_class = TotalContentProgressSerializer\n\n\nclass UserSessionLogFilter(BaseLogFilter):\n\n    class Meta:\n        model = UserSessionLog\n        fields = ['user_id']\n\n\nclass UserSessionLogViewSet(LoggerViewSet):\n    permission_classes = (KolibriAuthPermissions,)\n    filter_backends = (KolibriAuthPermissionsFilter, DjangoFilterBackend)\n    queryset = UserSessionLog.objects.all()\n    serializer_class = UserSessionLogSerializer\n    pagination_class = OptionalPageNumberPagination\n    filter_class = UserSessionLogFilter\n\n\nclass MasteryFilter(FilterSet):\n\n    class Meta:\n        model = MasteryLog\n        fields = ['summarylog']\n\n\nclass MasteryLogViewSet(LoggerViewSet):\n    permission_classes = (KolibriAuthPermissions,)\n    filter_backends = (KolibriAuthPermissionsFilter, DjangoFilterBackend)\n    queryset = MasteryLog.objects.all()\n    serializer_class = MasteryLogSerializer\n    pagination_class = OptionalPageNumberPagination\n    filter_class = MasteryFilter\n\n\nclass AttemptFilter(BaseLogFilter):\n    content = CharFilter(method=\"filter_content\")\n\n    def filter_content(self, queryset, name, value):\n        return queryset.filter(masterylog__summarylog__content_id=value)\n\n    class Meta:\n        model = AttemptLog\n        fields = ['masterylog', 'complete', 'user', 'content']\n\n\nclass AttemptLogViewSet(LoggerViewSet):\n    permission_classes = (KolibriAuthPermissions,)\n    filter_backends = (KolibriAuthPermissionsFilter, DjangoFilterBackend, filters.OrderingFilter)\n    queryset = AttemptLog.objects.all()\n    serializer_class = AttemptLogSerializer\n    pagination_class = OptionalPageNumberPagination\n    filter_class = AttemptFilter\n    ordering_fields = ('end_timestamp',)\n    ordering = ('end_timestamp',)\n\n\nclass ExamAttemptFilter(BaseLogFilter):\n    exam = ModelChoiceFilter(method=\"filter_exam\", queryset=Exam.objects.all())\n    user = ModelChoiceFilter(method=\"filter_user\", queryset=FacilityUser.objects.all())\n    content = CharFilter(field_name=\"content_id\")\n\n    def filter_exam(self, queryset, name, value):\n        return queryset.filter(examlog__exam=value)\n\n    def filter_user(self, queryset, name, value):\n        return queryset.filter(examlog__user=value)\n\n    class Meta:\n        model = ExamAttemptLog\n        fields = ['examlog', 'exam', 'user', 'content']\n\n\nclass ExamAttemptLogViewSet(LoggerViewSet):\n    permission_classes = (ExamActivePermissions, KolibriAuthPermissions, )\n    filter_backends = (KolibriAuthPermissionsFilter, DjangoFilterBackend, filters.OrderingFilter)\n    queryset = ExamAttemptLog.objects.all()\n    serializer_class = ExamAttemptLogSerializer\n    pagination_class = OptionalPageNumberPagination\n    filter_class = ExamAttemptFilter\n\n\nclass ExamLogFilter(BaseLogFilter):\n\n    collection = ModelChoiceFilter(method=\"filter_collection\", queryset=Collection.objects.all())\n\n    def filter_collection(self, queryset, name, collection):\n        return HierarchyRelationsFilter(queryset).filter_by_hierarchy(\n            target_user=F('user'),\n            ancestor_collection=collection,\n        )\n\n    class Meta:\n        model = ExamLog\n        fields = ['user', 'exam']\n\n\nclass ExamLogViewSet(viewsets.ModelViewSet):\n    permission_classes = (KolibriAuthPermissions,)\n    filter_backends = (KolibriAuthPermissionsFilter, DjangoFilterBackend)\n    queryset = ExamLog.objects.all()\n    serializer_class = ExamLogSerializer\n    pagination_class = OptionalPageNumberPagination\n    filter_class = ExamLogFilter\n", "path": "kolibri/core/logger/api.py"}], "after_files": [{"content": "from django.core.exceptions import ObjectDoesNotExist\nfrom django.db.models.query import F\nfrom django.http import Http404\nfrom django_filters import ModelChoiceFilter\nfrom django_filters.rest_framework import CharFilter\nfrom django_filters.rest_framework import DjangoFilterBackend\nfrom django_filters.rest_framework import FilterSet\nfrom rest_framework import filters\nfrom rest_framework import viewsets\nfrom rest_framework.response import Response\n\nfrom .models import AttemptLog\nfrom .models import ContentSessionLog\nfrom .models import ContentSummaryLog\nfrom .models import ExamAttemptLog\nfrom .models import ExamLog\nfrom .models import MasteryLog\nfrom .models import UserSessionLog\nfrom .permissions import ExamActivePermissions\nfrom .serializers import AttemptLogSerializer\nfrom .serializers import ContentSessionLogSerializer\nfrom .serializers import ContentSummaryLogSerializer\nfrom .serializers import ExamAttemptLogSerializer\nfrom .serializers import ExamLogSerializer\nfrom .serializers import MasteryLogSerializer\nfrom .serializers import TotalContentProgressSerializer\nfrom .serializers import UserSessionLogSerializer\nfrom kolibri.core.auth.api import KolibriAuthPermissions\nfrom kolibri.core.auth.api import KolibriAuthPermissionsFilter\nfrom kolibri.core.auth.filters import HierarchyRelationsFilter\nfrom kolibri.core.auth.models import Classroom\nfrom kolibri.core.auth.models import Collection\nfrom kolibri.core.auth.models import Facility\nfrom kolibri.core.auth.models import FacilityUser\nfrom kolibri.core.auth.models import LearnerGroup\nfrom kolibri.core.content.api import OptionalPageNumberPagination\nfrom kolibri.core.exams.models import Exam\n\n\nclass BaseLogFilter(FilterSet):\n    facility = ModelChoiceFilter(method=\"filter_facility\", queryset=Facility.objects.all())\n    classroom = ModelChoiceFilter(method=\"filter_classroom\", queryset=Classroom.objects.all())\n    learner_group = ModelChoiceFilter(method=\"filter_learner_group\", queryset=LearnerGroup.objects.all())\n\n    # Only a superuser can filter by facilities\n    def filter_facility(self, queryset, name, value):\n        return queryset.filter(user__facility=value)\n\n    def filter_classroom(self, queryset, name, value):\n        return HierarchyRelationsFilter(queryset).filter_by_hierarchy(\n            ancestor_collection=value,\n            target_user=F(\"user\"),\n        )\n\n    def filter_learner_group(self, queryset, name, value):\n        return HierarchyRelationsFilter(queryset).filter_by_hierarchy(\n            ancestor_collection=value,\n            target_user=F(\"user\"),\n        )\n\n\nclass LoggerViewSet(viewsets.ModelViewSet):\n    def update(self, request, *args, **kwargs):\n        partial = kwargs.pop('partial', False)\n        model = self.queryset.model\n        lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field\n        try:\n            instance = model.objects.get(id=self.kwargs[lookup_url_kwarg])\n            self.check_object_permissions(request, instance)\n        except (ValueError, ObjectDoesNotExist):\n            raise Http404\n        serializer = self.get_serializer(instance, data=request.data, partial=partial)\n        serializer.is_valid(raise_exception=True)\n        self.perform_update(serializer)\n\n        if getattr(instance, '_prefetched_objects_cache', None):\n            # If 'prefetch_related' has been applied to a queryset, we need to\n            # forcibly invalidate the prefetch cache on the instance.\n            instance._prefetched_objects_cache = {}\n        default_response = dict(request.data)\n        # First look if the computed fields to be updated are listed:\n        updating_fields = getattr(serializer.root, 'update_fields', None)\n        # If not, fetch all the fields that are computed methods:\n        if updating_fields is None:\n            updating_fields = [field for field in serializer.fields if getattr(serializer.fields[field], 'method_name', None)]\n        for field in updating_fields:\n            method_name = getattr(serializer.fields[field], 'method_name', None)\n            if method_name:\n                method = getattr(serializer.root, method_name)\n                default_response[field] = method(instance)\n        return Response(default_response)\n\n\nclass ContentSessionLogFilter(BaseLogFilter):\n\n    class Meta:\n        model = ContentSessionLog\n        fields = ['user_id', 'content_id']\n\n\nclass ContentSessionLogViewSet(LoggerViewSet):\n    permission_classes = (KolibriAuthPermissions,)\n    filter_backends = (KolibriAuthPermissionsFilter, DjangoFilterBackend)\n    queryset = ContentSessionLog.objects.all()\n    serializer_class = ContentSessionLogSerializer\n    pagination_class = OptionalPageNumberPagination\n    filter_class = ContentSessionLogFilter\n\n\nclass ContentSummaryLogFilter(BaseLogFilter):\n\n    class Meta:\n        model = ContentSummaryLog\n        fields = ['user_id', 'content_id']\n\n\nclass ContentSummaryLogViewSet(LoggerViewSet):\n    permission_classes = (KolibriAuthPermissions,)\n    filter_backends = (KolibriAuthPermissionsFilter, DjangoFilterBackend)\n    queryset = ContentSummaryLog.objects.all()\n    serializer_class = ContentSummaryLogSerializer\n    pagination_class = OptionalPageNumberPagination\n    filter_class = ContentSummaryLogFilter\n\n\nclass TotalContentProgressViewSet(viewsets.ModelViewSet):\n    permission_classes = (KolibriAuthPermissions,)\n    filter_backends = (KolibriAuthPermissionsFilter,)\n    queryset = FacilityUser.objects.all()\n    serializer_class = TotalContentProgressSerializer\n\n\nclass UserSessionLogFilter(BaseLogFilter):\n\n    class Meta:\n        model = UserSessionLog\n        fields = ['user_id']\n\n\nclass UserSessionLogViewSet(LoggerViewSet):\n    permission_classes = (KolibriAuthPermissions,)\n    filter_backends = (KolibriAuthPermissionsFilter, DjangoFilterBackend)\n    queryset = UserSessionLog.objects.all()\n    serializer_class = UserSessionLogSerializer\n    pagination_class = OptionalPageNumberPagination\n    filter_class = UserSessionLogFilter\n\n\nclass MasteryFilter(FilterSet):\n\n    class Meta:\n        model = MasteryLog\n        fields = ['summarylog']\n\n\nclass MasteryLogViewSet(LoggerViewSet):\n    permission_classes = (KolibriAuthPermissions,)\n    filter_backends = (KolibriAuthPermissionsFilter, DjangoFilterBackend)\n    queryset = MasteryLog.objects.all()\n    serializer_class = MasteryLogSerializer\n    pagination_class = OptionalPageNumberPagination\n    filter_class = MasteryFilter\n\n\nclass AttemptFilter(BaseLogFilter):\n    content = CharFilter(method=\"filter_content\")\n\n    def filter_content(self, queryset, name, value):\n        return queryset.filter(masterylog__summarylog__content_id=value)\n\n    class Meta:\n        model = AttemptLog\n        fields = ['masterylog', 'complete', 'user', 'content', 'item']\n\n\nclass AttemptLogViewSet(LoggerViewSet):\n    permission_classes = (KolibriAuthPermissions,)\n    filter_backends = (KolibriAuthPermissionsFilter, DjangoFilterBackend, filters.OrderingFilter)\n    queryset = AttemptLog.objects.all()\n    serializer_class = AttemptLogSerializer\n    pagination_class = OptionalPageNumberPagination\n    filter_class = AttemptFilter\n    ordering_fields = ('end_timestamp',)\n    ordering = ('end_timestamp',)\n\n\nclass ExamAttemptFilter(BaseLogFilter):\n    exam = ModelChoiceFilter(method=\"filter_exam\", queryset=Exam.objects.all())\n    user = ModelChoiceFilter(method=\"filter_user\", queryset=FacilityUser.objects.all())\n    content = CharFilter(field_name=\"content_id\")\n\n    def filter_exam(self, queryset, name, value):\n        return queryset.filter(examlog__exam=value)\n\n    def filter_user(self, queryset, name, value):\n        return queryset.filter(examlog__user=value)\n\n    class Meta:\n        model = ExamAttemptLog\n        fields = ['examlog', 'exam', 'user', 'content', 'item']\n\n\nclass ExamAttemptLogViewSet(LoggerViewSet):\n    permission_classes = (ExamActivePermissions, KolibriAuthPermissions, )\n    filter_backends = (KolibriAuthPermissionsFilter, DjangoFilterBackend, filters.OrderingFilter)\n    queryset = ExamAttemptLog.objects.all()\n    serializer_class = ExamAttemptLogSerializer\n    pagination_class = OptionalPageNumberPagination\n    filter_class = ExamAttemptFilter\n\n\nclass ExamLogFilter(BaseLogFilter):\n\n    collection = ModelChoiceFilter(method=\"filter_collection\", queryset=Collection.objects.all())\n\n    def filter_collection(self, queryset, name, collection):\n        return HierarchyRelationsFilter(queryset).filter_by_hierarchy(\n            target_user=F('user'),\n            ancestor_collection=collection,\n        )\n\n    class Meta:\n        model = ExamLog\n        fields = ['user', 'exam']\n\n\nclass ExamLogViewSet(viewsets.ModelViewSet):\n    permission_classes = (KolibriAuthPermissions,)\n    filter_backends = (KolibriAuthPermissionsFilter, DjangoFilterBackend)\n    queryset = ExamLog.objects.all()\n    serializer_class = ExamLogSerializer\n    pagination_class = OptionalPageNumberPagination\n    filter_class = ExamLogFilter\n", "path": "kolibri/core/logger/api.py"}]}