prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  [beta][v20] Lire une notification n'invalide pas le cache
  Serveur : Beta
  Version : v20-RC2/99bee1d
  Système : Mac OS X
  Navigateur : 52.0.2743.116 (64-bit)
  
  ---
  1. Générez une notification.
  2. Lisez là depuis le site.
  3. Récupérez la liste des notifications par l'API.
  4. Si le timeout de 15 minutes n'est pas passé par là, la notification est toujours marquée comme non lue dans la réponse de l'API.
  
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `zds/notification/api/views.py`
  Content:
  ```
  1 # coding: utf-8
  2 from dry_rest_permissions.generics import DRYPermissions
  3 from rest_framework import filters
  4 from rest_framework.generics import ListAPIView
  5 from rest_framework.permissions import IsAuthenticated
  6 from rest_framework_extensions.cache.decorators import cache_response
  7 from rest_framework_extensions.etag.decorators import etag
  8 from rest_framework_extensions.key_constructor import bits
  9 from rest_framework_extensions.key_constructor.constructors import DefaultKeyConstructor
  10 
  11 from zds.api.bits import DJRF3xPaginationKeyBit
  12 from zds.notification.api.serializers import NotificationSerializer
  13 from zds.notification.models import Notification
  14 
  15 
  16 class PagingNotificationListKeyConstructor(DefaultKeyConstructor):
  17     pagination = DJRF3xPaginationKeyBit()
  18     search = bits.QueryParamsKeyBit(['search', 'ordering', 'type'])
  19     list_sql_query = bits.ListSqlQueryKeyBit()
  20     unique_view_id = bits.UniqueViewIdKeyBit()
  21     user = bits.UserKeyBit()
  22 
  23 
  24 class NotificationListAPI(ListAPIView):
  25     """
  26     List of notification.
  27     """
  28 
  29     filter_backends = (filters.SearchFilter, filters.OrderingFilter)
  30     search_fields = ('title',)
  31     ordering_fields = ('pubdate', 'title',)
  32     list_key_func = PagingNotificationListKeyConstructor()
  33     serializer_class = NotificationSerializer
  34     permission_classes = (IsAuthenticated, DRYPermissions,)
  35 
  36     @etag(list_key_func)
  37     @cache_response(key_func=list_key_func)
  38     def get(self, request, *args, **kwargs):
  39         """
  40         Lists all notifications of a user.
  41         ---
  42 
  43         parameters:
  44             - name: Authorization
  45               description: Bearer token to make an authenticated request.
  46               required: true
  47               paramType: header
  48             - name: page
  49               description: Restricts output to the given page number.
  50               required: false
  51               paramType: query
  52             - name: page_size
  53               description: Sets the number of notifications per page.
  54               required: false
  55               paramType: query
  56             - name: search
  57               description: Filters by title.
  58               required: false
  59               paramType: query
  60             - name: ordering
  61               description: Sorts the results. You can order by (-)pubdate or (-)title.
  62               paramType: query
  63             - name: type
  64               description: Filters by notification type.
  65               paramType: query
  66             - name: subscription_type
  67               description: Filters by subscription type.
  68               paramType: query
  69             - name: expand
  70               description: Returns an object instead of an identifier representing the given field.
  71               required: false
  72               paramType: query
  73         responseMessages:
  74             - code: 401
  75               message: Not Authenticated
  76             - code: 404
  77               message: Not Found
  78         """
  79         return self.list(request, *args, **kwargs)
  80 
  81     def get_queryset(self):
  82         queryset = Notification.objects.get_notifications_of(self.request.user)
  83         subscription_type = self.request.query_params.get('subscription_type', None)
  84         if subscription_type:
  85             queryset = queryset.filter(subscription__content_type__model=subscription_type)
  86         _type = self.request.query_params.get('type', None)
  87         if _type:
  88             queryset = queryset.filter(content_type__model=_type)
  89         return queryset
  90
  ```
  --- 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()
  
  ```
  
golden_diff: diff --git a/zds/notification/api/views.py b/zds/notification/api/views.py
  --- a/zds/notification/api/views.py
  +++ b/zds/notification/api/views.py
  @@ -1,4 +1,8 @@
   # coding: utf-8
  +import datetime
  +from django.core.cache import cache
  +from django.db.models.signals import post_delete
  +from django.db.models.signals import post_save
   from dry_rest_permissions.generics import DRYPermissions
   from rest_framework import filters
   from rest_framework.generics import ListAPIView
  @@ -8,7 +12,7 @@
   from rest_framework_extensions.key_constructor import bits
   from rest_framework_extensions.key_constructor.constructors import DefaultKeyConstructor
   
  -from zds.api.bits import DJRF3xPaginationKeyBit
  +from zds.api.bits import DJRF3xPaginationKeyBit, UpdatedAtKeyBit
   from zds.notification.api.serializers import NotificationSerializer
   from zds.notification.models import Notification
   
  @@ -19,6 +23,15 @@
       list_sql_query = bits.ListSqlQueryKeyBit()
       unique_view_id = bits.UniqueViewIdKeyBit()
       user = bits.UserKeyBit()
  +    updated_at = UpdatedAtKeyBit('api_updated_notification')
  +
  +
  +def change_api_notification_updated_at(sender=None, instance=None, *args, **kwargs):
  +    cache.set('api_updated_notification', datetime.datetime.utcnow())
  +
  +
  +post_save.connect(receiver=change_api_notification_updated_at, sender=Notification)
  +post_delete.connect(receiver=change_api_notification_updated_at, sender=Notification)
   
   
   class NotificationListAPI(ListAPIView):
  
verification_info: {"golden_diff": "diff --git a/zds/notification/api/views.py b/zds/notification/api/views.py\n--- a/zds/notification/api/views.py\n+++ b/zds/notification/api/views.py\n@@ -1,4 +1,8 @@\n # coding: utf-8\n+import datetime\n+from django.core.cache import cache\n+from django.db.models.signals import post_delete\n+from django.db.models.signals import post_save\n from dry_rest_permissions.generics import DRYPermissions\n from rest_framework import filters\n from rest_framework.generics import ListAPIView\n@@ -8,7 +12,7 @@\n from rest_framework_extensions.key_constructor import bits\n from rest_framework_extensions.key_constructor.constructors import DefaultKeyConstructor\n \n-from zds.api.bits import DJRF3xPaginationKeyBit\n+from zds.api.bits import DJRF3xPaginationKeyBit, UpdatedAtKeyBit\n from zds.notification.api.serializers import NotificationSerializer\n from zds.notification.models import Notification\n \n@@ -19,6 +23,15 @@\n     list_sql_query = bits.ListSqlQueryKeyBit()\n     unique_view_id = bits.UniqueViewIdKeyBit()\n     user = bits.UserKeyBit()\n+    updated_at = UpdatedAtKeyBit('api_updated_notification')\n+\n+\n+def change_api_notification_updated_at(sender=None, instance=None, *args, **kwargs):\n+    cache.set('api_updated_notification', datetime.datetime.utcnow())\n+\n+\n+post_save.connect(receiver=change_api_notification_updated_at, sender=Notification)\n+post_delete.connect(receiver=change_api_notification_updated_at, sender=Notification)\n \n \n class NotificationListAPI(ListAPIView):\n", "issue": "[beta][v20] Lire une notification n'invalide pas le cache\nServeur : Beta\nVersion : v20-RC2/99bee1d\nSyst\u00e8me : Mac OS X\nNavigateur : 52.0.2743.116 (64-bit)\n\n---\n1. G\u00e9n\u00e9rez une notification.\n2. Lisez l\u00e0 depuis le site.\n3. R\u00e9cup\u00e9rez la liste des notifications par l'API.\n4. Si le timeout de 15 minutes n'est pas pass\u00e9 par l\u00e0, la notification est toujours marqu\u00e9e comme non lue dans la r\u00e9ponse de l'API.\n\n", "before_files": [{"content": "# coding: utf-8\nfrom dry_rest_permissions.generics import DRYPermissions\nfrom rest_framework import filters\nfrom rest_framework.generics import ListAPIView\nfrom rest_framework.permissions import IsAuthenticated\nfrom rest_framework_extensions.cache.decorators import cache_response\nfrom rest_framework_extensions.etag.decorators import etag\nfrom rest_framework_extensions.key_constructor import bits\nfrom rest_framework_extensions.key_constructor.constructors import DefaultKeyConstructor\n\nfrom zds.api.bits import DJRF3xPaginationKeyBit\nfrom zds.notification.api.serializers import NotificationSerializer\nfrom zds.notification.models import Notification\n\n\nclass PagingNotificationListKeyConstructor(DefaultKeyConstructor):\n    pagination = DJRF3xPaginationKeyBit()\n    search = bits.QueryParamsKeyBit(['search', 'ordering', 'type'])\n    list_sql_query = bits.ListSqlQueryKeyBit()\n    unique_view_id = bits.UniqueViewIdKeyBit()\n    user = bits.UserKeyBit()\n\n\nclass NotificationListAPI(ListAPIView):\n    \"\"\"\n    List of notification.\n    \"\"\"\n\n    filter_backends = (filters.SearchFilter, filters.OrderingFilter)\n    search_fields = ('title',)\n    ordering_fields = ('pubdate', 'title',)\n    list_key_func = PagingNotificationListKeyConstructor()\n    serializer_class = NotificationSerializer\n    permission_classes = (IsAuthenticated, DRYPermissions,)\n\n    @etag(list_key_func)\n    @cache_response(key_func=list_key_func)\n    def get(self, request, *args, **kwargs):\n        \"\"\"\n        Lists all notifications of a user.\n        ---\n\n        parameters:\n            - name: Authorization\n              description: Bearer token to make an authenticated request.\n              required: true\n              paramType: header\n            - name: page\n              description: Restricts output to the given page number.\n              required: false\n              paramType: query\n            - name: page_size\n              description: Sets the number of notifications per page.\n              required: false\n              paramType: query\n            - name: search\n              description: Filters by title.\n              required: false\n              paramType: query\n            - name: ordering\n              description: Sorts the results. You can order by (-)pubdate or (-)title.\n              paramType: query\n            - name: type\n              description: Filters by notification type.\n              paramType: query\n            - name: subscription_type\n              description: Filters by subscription type.\n              paramType: query\n            - name: expand\n              description: Returns an object instead of an identifier representing the given field.\n              required: false\n              paramType: query\n        responseMessages:\n            - code: 401\n              message: Not Authenticated\n            - code: 404\n              message: Not Found\n        \"\"\"\n        return self.list(request, *args, **kwargs)\n\n    def get_queryset(self):\n        queryset = Notification.objects.get_notifications_of(self.request.user)\n        subscription_type = self.request.query_params.get('subscription_type', None)\n        if subscription_type:\n            queryset = queryset.filter(subscription__content_type__model=subscription_type)\n        _type = self.request.query_params.get('type', None)\n        if _type:\n            queryset = queryset.filter(content_type__model=_type)\n        return queryset\n", "path": "zds/notification/api/views.py"}], "after_files": [{"content": "# coding: utf-8\nimport datetime\nfrom django.core.cache import cache\nfrom django.db.models.signals import post_delete\nfrom django.db.models.signals import post_save\nfrom dry_rest_permissions.generics import DRYPermissions\nfrom rest_framework import filters\nfrom rest_framework.generics import ListAPIView\nfrom rest_framework.permissions import IsAuthenticated\nfrom rest_framework_extensions.cache.decorators import cache_response\nfrom rest_framework_extensions.etag.decorators import etag\nfrom rest_framework_extensions.key_constructor import bits\nfrom rest_framework_extensions.key_constructor.constructors import DefaultKeyConstructor\n\nfrom zds.api.bits import DJRF3xPaginationKeyBit, UpdatedAtKeyBit\nfrom zds.notification.api.serializers import NotificationSerializer\nfrom zds.notification.models import Notification\n\n\nclass PagingNotificationListKeyConstructor(DefaultKeyConstructor):\n    pagination = DJRF3xPaginationKeyBit()\n    search = bits.QueryParamsKeyBit(['search', 'ordering', 'type'])\n    list_sql_query = bits.ListSqlQueryKeyBit()\n    unique_view_id = bits.UniqueViewIdKeyBit()\n    user = bits.UserKeyBit()\n    updated_at = UpdatedAtKeyBit('api_updated_notification')\n\n\ndef change_api_notification_updated_at(sender=None, instance=None, *args, **kwargs):\n    cache.set('api_updated_notification', datetime.datetime.utcnow())\n\n\npost_save.connect(receiver=change_api_notification_updated_at, sender=Notification)\npost_delete.connect(receiver=change_api_notification_updated_at, sender=Notification)\n\n\nclass NotificationListAPI(ListAPIView):\n    \"\"\"\n    List of notification.\n    \"\"\"\n\n    filter_backends = (filters.SearchFilter, filters.OrderingFilter)\n    search_fields = ('title',)\n    ordering_fields = ('pubdate', 'title',)\n    list_key_func = PagingNotificationListKeyConstructor()\n    serializer_class = NotificationSerializer\n    permission_classes = (IsAuthenticated, DRYPermissions,)\n\n    @etag(list_key_func)\n    @cache_response(key_func=list_key_func)\n    def get(self, request, *args, **kwargs):\n        \"\"\"\n        Lists all notifications of a user.\n        ---\n\n        parameters:\n            - name: Authorization\n              description: Bearer token to make an authenticated request.\n              required: true\n              paramType: header\n            - name: page\n              description: Restricts output to the given page number.\n              required: false\n              paramType: query\n            - name: page_size\n              description: Sets the number of notifications per page.\n              required: false\n              paramType: query\n            - name: search\n              description: Filters by title.\n              required: false\n              paramType: query\n            - name: ordering\n              description: Sorts the results. You can order by (-)pubdate or (-)title.\n              paramType: query\n            - name: type\n              description: Filters by notification type.\n              paramType: query\n            - name: subscription_type\n              description: Filters by subscription type.\n              paramType: query\n            - name: expand\n              description: Returns an object instead of an identifier representing the given field.\n              required: false\n              paramType: query\n        responseMessages:\n            - code: 401\n              message: Not Authenticated\n            - code: 404\n              message: Not Found\n        \"\"\"\n        return self.list(request, *args, **kwargs)\n\n    def get_queryset(self):\n        queryset = Notification.objects.get_notifications_of(self.request.user)\n        subscription_type = self.request.query_params.get('subscription_type', None)\n        if subscription_type:\n            queryset = queryset.filter(subscription__content_type__model=subscription_type)\n        _type = self.request.query_params.get('type', None)\n        if _type:\n            queryset = queryset.filter(content_type__model=_type)\n        return queryset\n", "path": "zds/notification/api/views.py"}]}