We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Compress HTML pages in the cache
Exercise and chapter pages are stored in the cache and only update if the backend reports that there is a change. Some pages might be large (e.g. 1M), but do compress relatively well. Memcached API supports compression on the fly, but that is not usable over django API.

Thus, we should at least compress HTML content manually. Alternatively, we can specialize `CachedAbstract` for memcached, which would also allow us to use `cas` operation.

Relevant files:
* `lib/cache/cached.py`
* `exercise/cache/exercise.py` (`content` in `_generate_data(...)` and `content()`)

--- 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/exercise.py`
Content:
```
1 import time
2 from django.conf import settings
3 from django.db.models.signals import post_save, post_delete
4 
5 from lib.cache import CachedAbstract
6 from lib.remote_page import RemotePageNotModified
7 from ..protocol.aplus import load_exercise_page
8 
9 
10 class ExerciseCache(CachedAbstract):
11     """ Exercise HTML content """
12     KEY_PREFIX = "exercise"
13 
14     def __init__(self, exercise, language, request, students, url_name):
15         self.exercise = exercise
16         self.load_args = [language, request, students, url_name]
17         super().__init__(exercise, modifiers=[language])
18 
19     def _needs_generation(self, data):
20         expires = data['expires'] if data else None
21         return not expires or time.time() > expires
22 
23     def _generate_data(self, exercise, data=None):
24         try:
25             page = exercise.load_page(
26                 *self.load_args,
27                 last_modified=data['last_modified'] if data else None
28             )
29             return {
30                 'head': page.head,
31                 'content': page.content,
32                 'last_modified': page.last_modified,
33                 'expires': page.expires if page.is_loaded else 0,
34             }
35         except RemotePageNotModified as e:
36             if e.expires:
37                 data['expires'] = e.expires
38             return data
39 
40     def head(self):
41         return self.data['head']
42 
43     def content(self):
44         return self.data['content']
45 
46 
47 def invalidate_instance(instance):
48     for module in instance.course_modules.all():
49         for exercise in module.learning_objects.all():
50             for language,_ in settings.LANGUAGES:
51                 ExerciseCache.invalidate(exercise, modifiers=[language])
52
```
--- 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/exercise.py b/exercise/cache/exercise.py
--- a/exercise/cache/exercise.py
+++ b/exercise/cache/exercise.py
@@ -1,4 +1,6 @@
+import logging
 import time
+
 from django.conf import settings
 from django.db.models.signals import post_save, post_delete
 
@@ -6,6 +8,18 @@
 from lib.remote_page import RemotePageNotModified
 from ..protocol.aplus import load_exercise_page
 
+logger = logging.getLogger('aplus.cached')
+
+try:
+    from lz4.block import compress as _compress, decompress
+    def compress(data):
+        return _compress(data, compression=1)
+except ImportError:
+    logger.warning("Unable to import lz4, using a slower zlib instead")
+    from zlib import compress as _compress, decompress
+    def compress(data):
+        return _compress(data, level=1)
+
 
 class ExerciseCache(CachedAbstract):
     """ Exercise HTML content """
@@ -26,9 +40,12 @@
                 *self.load_args,
                 last_modified=data['last_modified'] if data else None
             )
+
+            content = compress(page.content.encode('utf-8'))
+
             return {
                 'head': page.head,
-                'content': page.content,
+                'content': content,
                 'last_modified': page.last_modified,
                 'expires': page.expires if page.is_loaded else 0,
             }
@@ -41,7 +58,8 @@
         return self.data['head']
 
     def content(self):
-        return self.data['content']
+        content = decompress(self.data['content']).decode('utf-8')
+        return content
 
 
 def invalidate_instance(instance):

{"golden_diff": "diff --git a/exercise/cache/exercise.py b/exercise/cache/exercise.py\n--- a/exercise/cache/exercise.py\n+++ b/exercise/cache/exercise.py\n@@ -1,4 +1,6 @@\n+import logging\n import time\n+\n from django.conf import settings\n from django.db.models.signals import post_save, post_delete\n \n@@ -6,6 +8,18 @@\n from lib.remote_page import RemotePageNotModified\n from ..protocol.aplus import load_exercise_page\n \n+logger = logging.getLogger('aplus.cached')\n+\n+try:\n+    from lz4.block import compress as _compress, decompress\n+    def compress(data):\n+        return _compress(data, compression=1)\n+except ImportError:\n+    logger.warning(\"Unable to import lz4, using a slower zlib instead\")\n+    from zlib import compress as _compress, decompress\n+    def compress(data):\n+        return _compress(data, level=1)\n+\n \n class ExerciseCache(CachedAbstract):\n     \"\"\" Exercise HTML content \"\"\"\n@@ -26,9 +40,12 @@\n                 *self.load_args,\n                 last_modified=data['last_modified'] if data else None\n             )\n+\n+            content = compress(page.content.encode('utf-8'))\n+\n             return {\n                 'head': page.head,\n-                'content': page.content,\n+                'content': content,\n                 'last_modified': page.last_modified,\n                 'expires': page.expires if page.is_loaded else 0,\n             }\n@@ -41,7 +58,8 @@\n         return self.data['head']\n \n     def content(self):\n-        return self.data['content']\n+        content = decompress(self.data['content']).decode('utf-8')\n+        return content\n \n \n def invalidate_instance(instance):\n", "issue": "Compress HTML pages in the cache\nExercise and chapter pages are stored in the cache and only update if the backend reports that there is a change. Some pages might be large (e.g. 1M), but do compress relatively well. Memcached API supports compression on the fly, but that is not usable over django API.\r\n\r\nThus, we should at least compress HTML content manually. Alternatively, we can specialize `CachedAbstract` for memcached, which would also allow us to use `cas` operation.\r\n\r\nRelevant files:\r\n* `lib/cache/cached.py`\r\n* `exercise/cache/exercise.py` (`content` in `_generate_data(...)` and `content()`)\n", "before_files": [{"content": "import time\nfrom django.conf import settings\nfrom django.db.models.signals import post_save, post_delete\n\nfrom lib.cache import CachedAbstract\nfrom lib.remote_page import RemotePageNotModified\nfrom ..protocol.aplus import load_exercise_page\n\n\nclass ExerciseCache(CachedAbstract):\n    \"\"\" Exercise HTML content \"\"\"\n    KEY_PREFIX = \"exercise\"\n\n    def __init__(self, exercise, language, request, students, url_name):\n        self.exercise = exercise\n        self.load_args = [language, request, students, url_name]\n        super().__init__(exercise, modifiers=[language])\n\n    def _needs_generation(self, data):\n        expires = data['expires'] if data else None\n        return not expires or time.time() > expires\n\n    def _generate_data(self, exercise, data=None):\n        try:\n            page = exercise.load_page(\n                *self.load_args,\n                last_modified=data['last_modified'] if data else None\n            )\n            return {\n                'head': page.head,\n                'content': page.content,\n                'last_modified': page.last_modified,\n                'expires': page.expires if page.is_loaded else 0,\n            }\n        except RemotePageNotModified as e:\n            if e.expires:\n                data['expires'] = e.expires\n            return data\n\n    def head(self):\n        return self.data['head']\n\n    def content(self):\n        return self.data['content']\n\n\ndef invalidate_instance(instance):\n    for module in instance.course_modules.all():\n        for exercise in module.learning_objects.all():\n            for language,_ in settings.LANGUAGES:\n                ExerciseCache.invalidate(exercise, modifiers=[language])\n", "path": "exercise/cache/exercise.py"}], "after_files": [{"content": "import logging\nimport time\n\nfrom django.conf import settings\nfrom django.db.models.signals import post_save, post_delete\n\nfrom lib.cache import CachedAbstract\nfrom lib.remote_page import RemotePageNotModified\nfrom ..protocol.aplus import load_exercise_page\n\nlogger = logging.getLogger('aplus.cached')\n\ntry:\n    from lz4.block import compress as _compress, decompress\n    def compress(data):\n        return _compress(data, compression=1)\nexcept ImportError:\n    logger.warning(\"Unable to import lz4, using a slower zlib instead\")\n    from zlib import compress as _compress, decompress\n    def compress(data):\n        return _compress(data, level=1)\n\n\nclass ExerciseCache(CachedAbstract):\n    \"\"\" Exercise HTML content \"\"\"\n    KEY_PREFIX = \"exercise\"\n\n    def __init__(self, exercise, language, request, students, url_name):\n        self.exercise = exercise\n        self.load_args = [language, request, students, url_name]\n        super().__init__(exercise, modifiers=[language])\n\n    def _needs_generation(self, data):\n        expires = data['expires'] if data else None\n        return not expires or time.time() > expires\n\n    def _generate_data(self, exercise, data=None):\n        try:\n            page = exercise.load_page(\n                *self.load_args,\n                last_modified=data['last_modified'] if data else None\n            )\n\n            content = compress(page.content.encode('utf-8'))\n\n            return {\n                'head': page.head,\n                'content': content,\n                'last_modified': page.last_modified,\n                'expires': page.expires if page.is_loaded else 0,\n            }\n        except RemotePageNotModified as e:\n            if e.expires:\n                data['expires'] = e.expires\n            return data\n\n    def head(self):\n        return self.data['head']\n\n    def content(self):\n        content = decompress(self.data['content']).decode('utf-8')\n        return content\n\n\ndef invalidate_instance(instance):\n    for module in instance.course_modules.all():\n        for exercise in module.learning_objects.all():\n            for language,_ in settings.LANGUAGES:\n                ExerciseCache.invalidate(exercise, modifiers=[language])\n", "path": "exercise/cache/exercise.py"}]}