We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Hide exercises in the maintenance mode from the course table of contents
Exercises in maintenance mode (LearningObject.status maintenance) should be hidden from course table of contents.

Exercises with the status hidden are already hidden in the table of contents. The maintenance mode could be handled in the code in the same way.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `exercise/cache/hierarchy.py`
Content:
```
1 from django.http.response import Http404
2 from course.models import CourseModule, LearningObjectCategory
3 from ..models import LearningObject
4 
5 
6 class NoSuchContent(Exception):
7     pass
8 
9 
10 class HierarchyIterator:
11     # pylint: disable-next=too-many-arguments
12     def __init__(self, children, idx=None, tree=None, visited=False, enclosed=True):
13         if idx is None:
14             self._default_start(children)
15         else:
16             self.idx = idx.copy()
17             self.levels = [children]
18             if tree and len(tree) > 1:
19                 for entry in tree[:-1]:
20                     self.levels.append(entry['children'])
21         self.visited = visited
22         self.enclose_begun = not enclosed
23         self.enclose_ended = not enclosed
24 
25     def __iter__(self): # pylint: disable=non-iterator-returned
26         return self
27 
28 
29 class NextIterator(HierarchyIterator):
30 
31     def _default_start(self, children):
32         self.idx = [0]
33         self.levels = [children]
34 
35     def __next__(self):
36         if not self.enclose_begun:
37             self.enclose_begun = True
38             return {'type':'level','down':True}
39         i = self.idx[-1]
40         level = self.levels[-1]
41         if not self.visited:
42             if i < len(level):
43                 self.visited = True
44                 return level[i]
45         else:
46             children = level[i].get('children')
47             if children:
48                 self.levels.append(children)
49                 self.idx.append(0)
50                 self.visited = False
51                 return {'type':'level','down':True}
52             i += 1
53             if i < len(level):
54                 self.idx[-1] = i
55                 return level[i]
56         if len(self.idx) > 1:
57             self.idx = self.idx[:-1]
58             self.levels = self.levels[:-1]
59             self.idx[-1] += 1
60             self.visited = False
61             return {'type':'level','up':True}
62         if not self.enclose_ended:
63             self.enclose_ended = True
64             return {'type':'level','up':True}
65         raise StopIteration()
66 
67 
68 class PreviousIterator(HierarchyIterator):
69 
70     def _default_start(self, children):
71         self.idx = []
72         self.levels = []
73         self._goto_last(children)
74 
75     def _goto_last(self, children):
76         level = children
77         while level:
78             i = len(level) - 1
79             self.idx.append(i)
80             self.levels.append(level)
81             level = level[i].get('children')
82 
83     def __next__(self):
84         i = self.idx[-1]
85         level = self.levels[-1]
86         if not self.visited:
87             self.visited = True
88             return level[i]
89         if i > 0:
90             i -= 1
91             self.idx[-1] = i
92             self._goto_last(level[i].get('children'))
93             return self.levels[-1][self.idx[-1]]
94         if len(self.idx) > 1:
95             self.idx = self.idx[:-1]
96             self.levels = self.levels[:-1]
97             return self.levels[-1][self.idx[-1]]
98         raise StopIteration()
99 
100 
101 class ContentMixin:
102 
103     def created(self):
104         return self.data['created']
105 
106     def total(self):
107         return self.data['total']
108 
109     def modules(self):
110         return self.data['modules']
111 
112     def modules_flatted(self):
113         for module in self.data['modules']:
114             module['flatted'] = self.flat_module(module)
115         return self.data['modules']
116 
117     def categories(self):
118         categories = list(self.data['categories'].values())
119         categories.sort(key=lambda entry: entry['name'])
120         return categories
121 
122     def flat_module(self, module, enclosed=True):
123         modules = self.modules()
124         idx = self._model_idx(module)
125         tree = self._by_idx(modules, idx)
126         return NextIterator(tree[0]['children'], enclosed=enclosed)
127 
128     def flat_full(self):
129         return NextIterator(self.modules(), enclosed=False)
130 
131     def begin(self):
132         for entry in self.flat_full():
133             if entry['type'] == 'exercise':
134                 return entry
135         return None
136 
137     def find_path(self, module_id, path):
138         paths = self.data['paths'].get(module_id, {})
139         if path in paths:
140             return paths[path]
141         raise NoSuchContent()
142 
143     def find_number(self, number):
144         hit = None
145         search = self.modules()
146         parts = number.split('.')
147         for i in range(len(parts)):
148             number = '.'.join(parts[0:i+1])
149             for s in search:
150                 if s['number'] == number:
151                     hit = s
152                     search = hit['children']
153                     break
154             else:
155                 raise NoSuchContent()
156         return hit
157 
158     def find_category(self, category_id):
159         categories = self.data['categories']
160         if category_id in categories:
161             return categories[category_id]
162         raise NoSuchContent()
163 
164     def find(self, model):
165         modules = self.modules()
166         idx = self._model_idx(model)
167         tree = self._by_idx(modules, idx)
168         return (
169             tree[-1],
170             tree,
171             self._previous(idx, tree),
172             self._next(idx, tree),
173         )
174 
175     def search_exercises(self, **kwargs):
176         _, entries = self.search_entries(**kwargs)
177         return [e for e in entries if e['type'] == 'exercise']
178 
179     def search_entries(self, number=None, category_id=None, module_id=None, # noqa: MC0001
180                        exercise_id=None, filter_for_assistant=False, best=False, # pylint: disable=unused-argument
181                        raise_404=True):
182         entry = None
183         if number:
184             try:
185                 entry = self.find_number(number)
186                 if entry['type'] == 'module':
187                     module_id = entry['id']
188                 elif entry['type'] == 'exercise':
189                     exercise_id = entry['id']
190             except NoSuchContent:
191                 if raise_404:
192                     raise Http404() # pylint: disable=raise-missing-from
193                 raise
194         search = None
195         if exercise_id is not None:
196             search = { 'type': 'exercise', 'id': int(exercise_id) }
197         elif module_id is not None:
198             search = { 'type': 'module', 'id': int(module_id) }
199         if search:
200             try:
201                 idx = self._model_idx(search)
202             except NoSuchContent:
203                 if raise_404:
204                     raise Http404() # pylint: disable=raise-missing-from
205                 raise
206             tree = self._by_idx(self.modules(), idx)
207             if not entry:
208                 entry = tree[-1]
209         else:
210             tree = [{ 'type': 'all', 'children': self.modules() }]
211         exercises = []
212 
213         def recursion(entry):
214             if (
215                 entry['type'] == 'module' or ( # pylint: disable=too-many-boolean-expressions
216                     entry['type'] == 'exercise' and
217                     (category_id is None or entry['category_id'] == category_id) and
218                     (not filter_for_assistant or entry['allow_assistant_viewing'])
219                 )
220             ):
221                 exercises.append(entry)
222             for child in entry['children']:
223                 recursion(child)
224         recursion(tree[-1])
225         return entry, exercises
226 
227     def _previous(self, idx, tree):
228         for entry in PreviousIterator(self.modules(), idx, tree, visited=True):
229             if self.is_listed(entry):
230                 return entry
231         return None
232 
233     def _next(self, idx, tree):
234         for entry in NextIterator(self.modules(), idx, tree, visited=True, enclosed=False):
235             if self.is_listed(entry):
236                 return entry
237         return None
238 
239     def _model_idx(self, model):
240         def find(index, search):
241             if search in index:
242                 return index[search]
243             raise NoSuchContent()
244         entry_type = None
245         if isinstance(model, dict):
246             entry_type = model.get('type', None)
247             if entry_type == 'module':
248                 return find(self.data['module_index'], model['id'])
249             if entry_type == 'exercise':
250                 return find(self.data['exercise_index'], model['id'])
251         elif isinstance(model, CourseModule):
252             return find(self.data['module_index'], model.id)
253         elif isinstance(model, LearningObject):
254             return find(self.data['exercise_index'], model.id)
255         else:
256             raise NoSuchContent()
257 
258     @classmethod
259     def _by_idx(cls, hierarchy, idx):
260         tree = []
261         for i in idx:
262             entry = hierarchy[i]
263             hierarchy = entry['children']
264             tree.append(entry)
265         return tree
266 
267     @classmethod
268     def _add_by_difficulty(cls, to, difficulty, points):
269         if difficulty in to:
270             to[difficulty] += points
271         else:
272             to[difficulty] = points
273 
274     @classmethod
275     def is_visible(cls, entry):
276         t = entry['type']
277         if t == 'exercise':
278             return (
279                 entry.get('category_status') != LearningObjectCategory.STATUS.HIDDEN
280                 and entry.get('module_status') != CourseModule.STATUS.HIDDEN
281                 and not entry['status'] in (
282                     LearningObject.STATUS.HIDDEN,
283                     LearningObject.STATUS.ENROLLMENT,
284                     LearningObject.STATUS.ENROLLMENT_EXTERNAL,
285                 )
286             )
287         if t == 'module':
288             return entry['status'] != CourseModule.STATUS.HIDDEN
289         if t == 'category':
290             return not entry['status'] in (
291                 LearningObjectCategory.STATUS.HIDDEN,
292                 LearningObjectCategory.STATUS.NOTOTAL,
293             )
294         return False
295 
296     @classmethod
297     def is_listed(cls, entry):
298         if not cls.is_visible(entry):
299             return False
300         t = entry['type']
301         if t == 'exercise':
302             return (
303                 entry.get('category_status') != LearningObjectCategory.STATUS.HIDDEN
304                 and entry.get('module_status') != CourseModule.STATUS.UNLISTED
305                 and entry['status'] != LearningObject.STATUS.UNLISTED
306             )
307         if t == 'module':
308             return entry['status'] != CourseModule.STATUS.UNLISTED
309         if t == 'category':
310             return entry['status'] != LearningObjectCategory.STATUS.HIDDEN
311         return True
312 
313     @classmethod
314     def is_in_maintenance(cls, entry):
315         t = entry['type']
316         if t == 'exercise':
317             return (
318                 entry['module_status'] == CourseModule.STATUS.MAINTENANCE
319                 or entry['status'] == LearningObject.STATUS.MAINTENANCE
320             )
321         if t == 'module':
322             return entry['status'] == CourseModule.STATUS.MAINTENANCE
323         return False
324
```
--- 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/exercise/cache/hierarchy.py b/exercise/cache/hierarchy.py
--- a/exercise/cache/hierarchy.py
+++ b/exercise/cache/hierarchy.py
@@ -302,7 +302,10 @@
             return (
                 entry.get('category_status') != LearningObjectCategory.STATUS.HIDDEN
                 and entry.get('module_status') != CourseModule.STATUS.UNLISTED
-                and entry['status'] != LearningObject.STATUS.UNLISTED
+                and not entry['status'] in (
+                    LearningObject.STATUS.UNLISTED,
+                    LearningObject.STATUS.MAINTENANCE,
+                )
             )
         if t == 'module':
             return entry['status'] != CourseModule.STATUS.UNLISTED

{"golden_diff": "diff --git a/exercise/cache/hierarchy.py b/exercise/cache/hierarchy.py\n--- a/exercise/cache/hierarchy.py\n+++ b/exercise/cache/hierarchy.py\n@@ -302,7 +302,10 @@\n             return (\n                 entry.get('category_status') != LearningObjectCategory.STATUS.HIDDEN\n                 and entry.get('module_status') != CourseModule.STATUS.UNLISTED\n-                and entry['status'] != LearningObject.STATUS.UNLISTED\n+                and not entry['status'] in (\n+                    LearningObject.STATUS.UNLISTED,\n+                    LearningObject.STATUS.MAINTENANCE,\n+                )\n             )\n         if t == 'module':\n             return entry['status'] != CourseModule.STATUS.UNLISTED\n", "issue": "Hide exercises in the maintenance mode from the course table of contents\nExercises in maintenance mode (LearningObject.status maintenance) should be hidden from course table of contents.\r\n\r\nExercises with the status hidden are already hidden in the table of contents. The maintenance mode could be handled in the code in the same way.\r\n\n", "before_files": [{"content": "from django.http.response import Http404\nfrom course.models import CourseModule, LearningObjectCategory\nfrom ..models import LearningObject\n\n\nclass NoSuchContent(Exception):\n    pass\n\n\nclass HierarchyIterator:\n    # pylint: disable-next=too-many-arguments\n    def __init__(self, children, idx=None, tree=None, visited=False, enclosed=True):\n        if idx is None:\n            self._default_start(children)\n        else:\n            self.idx = idx.copy()\n            self.levels = [children]\n            if tree and len(tree) > 1:\n                for entry in tree[:-1]:\n                    self.levels.append(entry['children'])\n        self.visited = visited\n        self.enclose_begun = not enclosed\n        self.enclose_ended = not enclosed\n\n    def __iter__(self): # pylint: disable=non-iterator-returned\n        return self\n\n\nclass NextIterator(HierarchyIterator):\n\n    def _default_start(self, children):\n        self.idx = [0]\n        self.levels = [children]\n\n    def __next__(self):\n        if not self.enclose_begun:\n            self.enclose_begun = True\n            return {'type':'level','down':True}\n        i = self.idx[-1]\n        level = self.levels[-1]\n        if not self.visited:\n            if i < len(level):\n                self.visited = True\n                return level[i]\n        else:\n            children = level[i].get('children')\n            if children:\n                self.levels.append(children)\n                self.idx.append(0)\n                self.visited = False\n                return {'type':'level','down':True}\n            i += 1\n            if i < len(level):\n                self.idx[-1] = i\n                return level[i]\n        if len(self.idx) > 1:\n            self.idx = self.idx[:-1]\n            self.levels = self.levels[:-1]\n            self.idx[-1] += 1\n            self.visited = False\n            return {'type':'level','up':True}\n        if not self.enclose_ended:\n            self.enclose_ended = True\n            return {'type':'level','up':True}\n        raise StopIteration()\n\n\nclass PreviousIterator(HierarchyIterator):\n\n    def _default_start(self, children):\n        self.idx = []\n        self.levels = []\n        self._goto_last(children)\n\n    def _goto_last(self, children):\n        level = children\n        while level:\n            i = len(level) - 1\n            self.idx.append(i)\n            self.levels.append(level)\n            level = level[i].get('children')\n\n    def __next__(self):\n        i = self.idx[-1]\n        level = self.levels[-1]\n        if not self.visited:\n            self.visited = True\n            return level[i]\n        if i > 0:\n            i -= 1\n            self.idx[-1] = i\n            self._goto_last(level[i].get('children'))\n            return self.levels[-1][self.idx[-1]]\n        if len(self.idx) > 1:\n            self.idx = self.idx[:-1]\n            self.levels = self.levels[:-1]\n            return self.levels[-1][self.idx[-1]]\n        raise StopIteration()\n\n\nclass ContentMixin:\n\n    def created(self):\n        return self.data['created']\n\n    def total(self):\n        return self.data['total']\n\n    def modules(self):\n        return self.data['modules']\n\n    def modules_flatted(self):\n        for module in self.data['modules']:\n            module['flatted'] = self.flat_module(module)\n        return self.data['modules']\n\n    def categories(self):\n        categories = list(self.data['categories'].values())\n        categories.sort(key=lambda entry: entry['name'])\n        return categories\n\n    def flat_module(self, module, enclosed=True):\n        modules = self.modules()\n        idx = self._model_idx(module)\n        tree = self._by_idx(modules, idx)\n        return NextIterator(tree[0]['children'], enclosed=enclosed)\n\n    def flat_full(self):\n        return NextIterator(self.modules(), enclosed=False)\n\n    def begin(self):\n        for entry in self.flat_full():\n            if entry['type'] == 'exercise':\n                return entry\n        return None\n\n    def find_path(self, module_id, path):\n        paths = self.data['paths'].get(module_id, {})\n        if path in paths:\n            return paths[path]\n        raise NoSuchContent()\n\n    def find_number(self, number):\n        hit = None\n        search = self.modules()\n        parts = number.split('.')\n        for i in range(len(parts)):\n            number = '.'.join(parts[0:i+1])\n            for s in search:\n                if s['number'] == number:\n                    hit = s\n                    search = hit['children']\n                    break\n            else:\n                raise NoSuchContent()\n        return hit\n\n    def find_category(self, category_id):\n        categories = self.data['categories']\n        if category_id in categories:\n            return categories[category_id]\n        raise NoSuchContent()\n\n    def find(self, model):\n        modules = self.modules()\n        idx = self._model_idx(model)\n        tree = self._by_idx(modules, idx)\n        return (\n            tree[-1],\n            tree,\n            self._previous(idx, tree),\n            self._next(idx, tree),\n        )\n\n    def search_exercises(self, **kwargs):\n        _, entries = self.search_entries(**kwargs)\n        return [e for e in entries if e['type'] == 'exercise']\n\n    def search_entries(self, number=None, category_id=None, module_id=None, # noqa: MC0001\n                       exercise_id=None, filter_for_assistant=False, best=False, # pylint: disable=unused-argument\n                       raise_404=True):\n        entry = None\n        if number:\n            try:\n                entry = self.find_number(number)\n                if entry['type'] == 'module':\n                    module_id = entry['id']\n                elif entry['type'] == 'exercise':\n                    exercise_id = entry['id']\n            except NoSuchContent:\n                if raise_404:\n                    raise Http404() # pylint: disable=raise-missing-from\n                raise\n        search = None\n        if exercise_id is not None:\n            search = { 'type': 'exercise', 'id': int(exercise_id) }\n        elif module_id is not None:\n            search = { 'type': 'module', 'id': int(module_id) }\n        if search:\n            try:\n                idx = self._model_idx(search)\n            except NoSuchContent:\n                if raise_404:\n                    raise Http404() # pylint: disable=raise-missing-from\n                raise\n            tree = self._by_idx(self.modules(), idx)\n            if not entry:\n                entry = tree[-1]\n        else:\n            tree = [{ 'type': 'all', 'children': self.modules() }]\n        exercises = []\n\n        def recursion(entry):\n            if (\n                entry['type'] == 'module' or ( # pylint: disable=too-many-boolean-expressions\n                    entry['type'] == 'exercise' and\n                    (category_id is None or entry['category_id'] == category_id) and\n                    (not filter_for_assistant or entry['allow_assistant_viewing'])\n                )\n            ):\n                exercises.append(entry)\n            for child in entry['children']:\n                recursion(child)\n        recursion(tree[-1])\n        return entry, exercises\n\n    def _previous(self, idx, tree):\n        for entry in PreviousIterator(self.modules(), idx, tree, visited=True):\n            if self.is_listed(entry):\n                return entry\n        return None\n\n    def _next(self, idx, tree):\n        for entry in NextIterator(self.modules(), idx, tree, visited=True, enclosed=False):\n            if self.is_listed(entry):\n                return entry\n        return None\n\n    def _model_idx(self, model):\n        def find(index, search):\n            if search in index:\n                return index[search]\n            raise NoSuchContent()\n        entry_type = None\n        if isinstance(model, dict):\n            entry_type = model.get('type', None)\n            if entry_type == 'module':\n                return find(self.data['module_index'], model['id'])\n            if entry_type == 'exercise':\n                return find(self.data['exercise_index'], model['id'])\n        elif isinstance(model, CourseModule):\n            return find(self.data['module_index'], model.id)\n        elif isinstance(model, LearningObject):\n            return find(self.data['exercise_index'], model.id)\n        else:\n            raise NoSuchContent()\n\n    @classmethod\n    def _by_idx(cls, hierarchy, idx):\n        tree = []\n        for i in idx:\n            entry = hierarchy[i]\n            hierarchy = entry['children']\n            tree.append(entry)\n        return tree\n\n    @classmethod\n    def _add_by_difficulty(cls, to, difficulty, points):\n        if difficulty in to:\n            to[difficulty] += points\n        else:\n            to[difficulty] = points\n\n    @classmethod\n    def is_visible(cls, entry):\n        t = entry['type']\n        if t == 'exercise':\n            return (\n                entry.get('category_status') != LearningObjectCategory.STATUS.HIDDEN\n                and entry.get('module_status') != CourseModule.STATUS.HIDDEN\n                and not entry['status'] in (\n                    LearningObject.STATUS.HIDDEN,\n                    LearningObject.STATUS.ENROLLMENT,\n                    LearningObject.STATUS.ENROLLMENT_EXTERNAL,\n                )\n            )\n        if t == 'module':\n            return entry['status'] != CourseModule.STATUS.HIDDEN\n        if t == 'category':\n            return not entry['status'] in (\n                LearningObjectCategory.STATUS.HIDDEN,\n                LearningObjectCategory.STATUS.NOTOTAL,\n            )\n        return False\n\n    @classmethod\n    def is_listed(cls, entry):\n        if not cls.is_visible(entry):\n            return False\n        t = entry['type']\n        if t == 'exercise':\n            return (\n                entry.get('category_status') != LearningObjectCategory.STATUS.HIDDEN\n                and entry.get('module_status') != CourseModule.STATUS.UNLISTED\n                and entry['status'] != LearningObject.STATUS.UNLISTED\n            )\n        if t == 'module':\n            return entry['status'] != CourseModule.STATUS.UNLISTED\n        if t == 'category':\n            return entry['status'] != LearningObjectCategory.STATUS.HIDDEN\n        return True\n\n    @classmethod\n    def is_in_maintenance(cls, entry):\n        t = entry['type']\n        if t == 'exercise':\n            return (\n                entry['module_status'] == CourseModule.STATUS.MAINTENANCE\n                or entry['status'] == LearningObject.STATUS.MAINTENANCE\n            )\n        if t == 'module':\n            return entry['status'] == CourseModule.STATUS.MAINTENANCE\n        return False\n", "path": "exercise/cache/hierarchy.py"}], "after_files": [{"content": "from django.http.response import Http404\nfrom course.models import CourseModule, LearningObjectCategory\nfrom ..models import LearningObject\n\n\nclass NoSuchContent(Exception):\n    pass\n\n\nclass HierarchyIterator:\n    # pylint: disable-next=too-many-arguments\n    def __init__(self, children, idx=None, tree=None, visited=False, enclosed=True):\n        if idx is None:\n            self._default_start(children)\n        else:\n            self.idx = idx.copy()\n            self.levels = [children]\n            if tree and len(tree) > 1:\n                for entry in tree[:-1]:\n                    self.levels.append(entry['children'])\n        self.visited = visited\n        self.enclose_begun = not enclosed\n        self.enclose_ended = not enclosed\n\n    def __iter__(self): # pylint: disable=non-iterator-returned\n        return self\n\n\nclass NextIterator(HierarchyIterator):\n\n    def _default_start(self, children):\n        self.idx = [0]\n        self.levels = [children]\n\n    def __next__(self):\n        if not self.enclose_begun:\n            self.enclose_begun = True\n            return {'type':'level','down':True}\n        i = self.idx[-1]\n        level = self.levels[-1]\n        if not self.visited:\n            if i < len(level):\n                self.visited = True\n                return level[i]\n        else:\n            children = level[i].get('children')\n            if children:\n                self.levels.append(children)\n                self.idx.append(0)\n                self.visited = False\n                return {'type':'level','down':True}\n            i += 1\n            if i < len(level):\n                self.idx[-1] = i\n                return level[i]\n        if len(self.idx) > 1:\n            self.idx = self.idx[:-1]\n            self.levels = self.levels[:-1]\n            self.idx[-1] += 1\n            self.visited = False\n            return {'type':'level','up':True}\n        if not self.enclose_ended:\n            self.enclose_ended = True\n            return {'type':'level','up':True}\n        raise StopIteration()\n\n\nclass PreviousIterator(HierarchyIterator):\n\n    def _default_start(self, children):\n        self.idx = []\n        self.levels = []\n        self._goto_last(children)\n\n    def _goto_last(self, children):\n        level = children\n        while level:\n            i = len(level) - 1\n            self.idx.append(i)\n            self.levels.append(level)\n            level = level[i].get('children')\n\n    def __next__(self):\n        i = self.idx[-1]\n        level = self.levels[-1]\n        if not self.visited:\n            self.visited = True\n            return level[i]\n        if i > 0:\n            i -= 1\n            self.idx[-1] = i\n            self._goto_last(level[i].get('children'))\n            return self.levels[-1][self.idx[-1]]\n        if len(self.idx) > 1:\n            self.idx = self.idx[:-1]\n            self.levels = self.levels[:-1]\n            return self.levels[-1][self.idx[-1]]\n        raise StopIteration()\n\n\nclass ContentMixin:\n\n    def created(self):\n        return self.data['created']\n\n    def total(self):\n        return self.data['total']\n\n    def modules(self):\n        return self.data['modules']\n\n    def modules_flatted(self):\n        for module in self.data['modules']:\n            module['flatted'] = self.flat_module(module)\n        return self.data['modules']\n\n    def categories(self):\n        categories = list(self.data['categories'].values())\n        categories.sort(key=lambda entry: entry['name'])\n        return categories\n\n    def flat_module(self, module, enclosed=True):\n        modules = self.modules()\n        idx = self._model_idx(module)\n        tree = self._by_idx(modules, idx)\n        return NextIterator(tree[0]['children'], enclosed=enclosed)\n\n    def flat_full(self):\n        return NextIterator(self.modules(), enclosed=False)\n\n    def begin(self):\n        for entry in self.flat_full():\n            if entry['type'] == 'exercise':\n                return entry\n        return None\n\n    def find_path(self, module_id, path):\n        paths = self.data['paths'].get(module_id, {})\n        if path in paths:\n            return paths[path]\n        raise NoSuchContent()\n\n    def find_number(self, number):\n        hit = None\n        search = self.modules()\n        parts = number.split('.')\n        for i in range(len(parts)):\n            number = '.'.join(parts[0:i+1])\n            for s in search:\n                if s['number'] == number:\n                    hit = s\n                    search = hit['children']\n                    break\n            else:\n                raise NoSuchContent()\n        return hit\n\n    def find_category(self, category_id):\n        categories = self.data['categories']\n        if category_id in categories:\n            return categories[category_id]\n        raise NoSuchContent()\n\n    def find(self, model):\n        modules = self.modules()\n        idx = self._model_idx(model)\n        tree = self._by_idx(modules, idx)\n        return (\n            tree[-1],\n            tree,\n            self._previous(idx, tree),\n            self._next(idx, tree),\n        )\n\n    def search_exercises(self, **kwargs):\n        _, entries = self.search_entries(**kwargs)\n        return [e for e in entries if e['type'] == 'exercise']\n\n    def search_entries(self, number=None, category_id=None, module_id=None, # noqa: MC0001\n                       exercise_id=None, filter_for_assistant=False, best=False, # pylint: disable=unused-argument\n                       raise_404=True):\n        entry = None\n        if number:\n            try:\n                entry = self.find_number(number)\n                if entry['type'] == 'module':\n                    module_id = entry['id']\n                elif entry['type'] == 'exercise':\n                    exercise_id = entry['id']\n            except NoSuchContent:\n                if raise_404:\n                    raise Http404() # pylint: disable=raise-missing-from\n                raise\n        search = None\n        if exercise_id is not None:\n            search = { 'type': 'exercise', 'id': int(exercise_id) }\n        elif module_id is not None:\n            search = { 'type': 'module', 'id': int(module_id) }\n        if search:\n            try:\n                idx = self._model_idx(search)\n            except NoSuchContent:\n                if raise_404:\n                    raise Http404() # pylint: disable=raise-missing-from\n                raise\n            tree = self._by_idx(self.modules(), idx)\n            if not entry:\n                entry = tree[-1]\n        else:\n            tree = [{ 'type': 'all', 'children': self.modules() }]\n        exercises = []\n\n        def recursion(entry):\n            if (\n                entry['type'] == 'module' or ( # pylint: disable=too-many-boolean-expressions\n                    entry['type'] == 'exercise' and\n                    (category_id is None or entry['category_id'] == category_id) and\n                    (not filter_for_assistant or entry['allow_assistant_viewing'])\n                )\n            ):\n                exercises.append(entry)\n            for child in entry['children']:\n                recursion(child)\n        recursion(tree[-1])\n        return entry, exercises\n\n    def _previous(self, idx, tree):\n        for entry in PreviousIterator(self.modules(), idx, tree, visited=True):\n            if self.is_listed(entry):\n                return entry\n        return None\n\n    def _next(self, idx, tree):\n        for entry in NextIterator(self.modules(), idx, tree, visited=True, enclosed=False):\n            if self.is_listed(entry):\n                return entry\n        return None\n\n    def _model_idx(self, model):\n        def find(index, search):\n            if search in index:\n                return index[search]\n            raise NoSuchContent()\n        entry_type = None\n        if isinstance(model, dict):\n            entry_type = model.get('type', None)\n            if entry_type == 'module':\n                return find(self.data['module_index'], model['id'])\n            if entry_type == 'exercise':\n                return find(self.data['exercise_index'], model['id'])\n        elif isinstance(model, CourseModule):\n            return find(self.data['module_index'], model.id)\n        elif isinstance(model, LearningObject):\n            return find(self.data['exercise_index'], model.id)\n        else:\n            raise NoSuchContent()\n\n    @classmethod\n    def _by_idx(cls, hierarchy, idx):\n        tree = []\n        for i in idx:\n            entry = hierarchy[i]\n            hierarchy = entry['children']\n            tree.append(entry)\n        return tree\n\n    @classmethod\n    def _add_by_difficulty(cls, to, difficulty, points):\n        if difficulty in to:\n            to[difficulty] += points\n        else:\n            to[difficulty] = points\n\n    @classmethod\n    def is_visible(cls, entry):\n        t = entry['type']\n        if t == 'exercise':\n            return (\n                entry.get('category_status') != LearningObjectCategory.STATUS.HIDDEN\n                and entry.get('module_status') != CourseModule.STATUS.HIDDEN\n                and not entry['status'] in (\n                    LearningObject.STATUS.HIDDEN,\n                    LearningObject.STATUS.ENROLLMENT,\n                    LearningObject.STATUS.ENROLLMENT_EXTERNAL,\n                )\n            )\n        if t == 'module':\n            return entry['status'] != CourseModule.STATUS.HIDDEN\n        if t == 'category':\n            return not entry['status'] in (\n                LearningObjectCategory.STATUS.HIDDEN,\n                LearningObjectCategory.STATUS.NOTOTAL,\n            )\n        return False\n\n    @classmethod\n    def is_listed(cls, entry):\n        if not cls.is_visible(entry):\n            return False\n        t = entry['type']\n        if t == 'exercise':\n            return (\n                entry.get('category_status') != LearningObjectCategory.STATUS.HIDDEN\n                and entry.get('module_status') != CourseModule.STATUS.UNLISTED\n                and not entry['status'] in (\n                    LearningObject.STATUS.UNLISTED,\n                    LearningObject.STATUS.MAINTENANCE,\n                )\n            )\n        if t == 'module':\n            return entry['status'] != CourseModule.STATUS.UNLISTED\n        if t == 'category':\n            return entry['status'] != LearningObjectCategory.STATUS.HIDDEN\n        return True\n\n    @classmethod\n    def is_in_maintenance(cls, entry):\n        t = entry['type']\n        if t == 'exercise':\n            return (\n                entry['module_status'] == CourseModule.STATUS.MAINTENANCE\n                or entry['status'] == LearningObject.STATUS.MAINTENANCE\n            )\n        if t == 'module':\n            return entry['status'] == CourseModule.STATUS.MAINTENANCE\n        return False\n", "path": "exercise/cache/hierarchy.py"}]}