prompt: You will be provided with a partial code base and an issue statement explaining a problem to resolve.
  <issue>
  Learner - Cannot change username and full name
  ## Observed behavior
  The fields for editing a learner's username and full name are disabled and I cannot change them.
  
  ## Expected behavior
  It should be possible for a learner to edit their username and full name if the options are enabled in Facility settings
  
  ## Steps to reproduce the issue
  1. Install the following [build.](https://github.com/learningequality/kolibri/releases/tag/v0.15.0-beta2)
  2. Create a facility with enabled options for 'Allow learners to edit their username' and 'Allow learners to edit their full name' in Facility settings
  3. Create a Learner user
  4. Sign in with the Learner, go to Profile and select the Edit button.
  5. Attempt to edit the Full name and Username fields.
  
  ## Additional information
  ![2021-11-15_15-21-13](https://user-images.githubusercontent.com/79847249/141820419-c2511e0e-0ff2-475a-a500-9fdedfafa76a.png)
  
  Logs: 
  [logs.zip](https://github.com/learningequality/kolibri/files/7540298/logs.zip)
  
  ## Usage Details
   - OS: Windows 10
   - Browser: Chrome
  
  </issue>
  <code>
  [start of kolibri/core/auth/middleware.py]
  1 from django.apps import apps
  2 from django.conf import settings
  3 from django.contrib.auth import _get_user_session_key
  4 from django.contrib.auth import get_user
  5 from django.contrib.auth.middleware import AuthenticationMiddleware
  6 from django.contrib.sessions.middleware import SessionMiddleware
  7 from django.core.cache import cache
  8 from django.core.exceptions import ImproperlyConfigured
  9 from django.utils.functional import SimpleLazyObject
  10 
  11 
  12 def get_anonymous_user_model():
  13     """
  14     Return the Anonymous User model that is active in this project.
  15     """
  16     try:
  17         app_name = settings.AUTH_ANONYMOUS_USER_MODEL.split(".")[0]
  18     except AttributeError:
  19         raise ImproperlyConfigured("AUTH_ANONYMOUS_USER_MODEL is not a string")
  20     try:
  21         model_name = settings.AUTH_ANONYMOUS_USER_MODEL.split(".")[1]
  22         app = apps.get_app_config(app_name)
  23         models_module = app.models_module
  24     except IndexError:
  25         raise ImproperlyConfigured(
  26             "AUTH_ANONYMOUS_USER_MODEL must be of the form 'app_label.model_name'"
  27         )
  28     except LookupError:
  29         raise ImproperlyConfigured(
  30             "AUTH_ANONYMOUS_USER_MODEL refers to an app '{}' that has not been installed".format(
  31                 app_name
  32             )
  33         )
  34     try:
  35         return getattr(models_module, model_name)
  36     except AttributeError:
  37         raise ImproperlyConfigured(
  38             "AUTH_ANONYMOUS_USER_MODEL refers to a model '{}' that does not exist in the app '{}'".format(
  39                 model_name, app_name
  40             )
  41         )
  42 
  43 
  44 def _get_user(request):
  45     if not hasattr(request, "_cached_user"):
  46         try:
  47             user_id = _get_user_session_key(request)
  48             USER_CACHE_KEY = "USER_BY_SESSION_CACHE_{}".format(user_id)
  49             user = cache.get(USER_CACHE_KEY)
  50             if not user:
  51                 user = get_user(request)
  52                 cache.set(USER_CACHE_KEY, user)
  53         except KeyError:
  54             user = get_user(request)
  55         if user.is_anonymous():
  56             AnonymousUser = get_anonymous_user_model()
  57             user = AnonymousUser()
  58         request._cached_user = user
  59 
  60     return request._cached_user
  61 
  62 
  63 class CustomAuthenticationMiddleware(AuthenticationMiddleware):
  64     """
  65     Adaptation of Django's ``account.middleware.AuthenticationMiddleware``
  66     to replace the default AnonymousUser with a custom implementation.
  67     """
  68 
  69     def process_request(self, request):
  70         if not hasattr(request, "session"):
  71             raise AssertionError(
  72                 "The authentication middleware requires session middleware "
  73                 "to be installed. Edit your MIDDLEWARE_CLASSES setting to insert "
  74                 "'django.contrib.sessions.middleware.SessionMiddleware' before "
  75                 "'kolibri.core.auth.middleware.CustomAuthenticationMiddleware'."
  76             )
  77         request.user = SimpleLazyObject(lambda: _get_user(request))
  78 
  79 
  80 class XhrPreventLoginPromptMiddleware(object):
  81     """
  82     By default, HTTP 401 responses are sent with a ``WWW-Authenticate``
  83     header. Web browsers react to this header by displaying a login prompt
  84     dialog.  By removing the header, the login prompt can be avoided.  While
  85     this isn't recommended in general, there's a convention of removing it
  86     for XHR requests, so that unauthenticated XHR requests don't trigger a
  87     popup.
  88 
  89     See `here <https://stackoverflow.com/a/20221330>`_ for reference.
  90     """
  91 
  92     def __init__(self, get_response):
  93         self.get_response = get_response
  94 
  95     def __call__(self, request):
  96         response = self.get_response(request)
  97         if response and response.status_code == 401 and request.is_ajax():
  98             del response["WWW-Authenticate"]
  99         return response
  100 
  101 
  102 SESSION_EXEMPT = "_session_exempt"
  103 
  104 
  105 def session_exempt(view):
  106     def wrapper_func(*args, **kwargs):
  107         return view(*args, **kwargs)
  108 
  109     setattr(wrapper_func, SESSION_EXEMPT, True)
  110     return wrapper_func
  111 
  112 
  113 class KolibriSessionMiddleware(SessionMiddleware):
  114     def _is_exempt(self, obj):
  115         return hasattr(obj, SESSION_EXEMPT)
  116 
  117     def process_view(self, request, callback, callback_args, callback_kwargs):
  118         if self._is_exempt(callback):
  119             setattr(request, SESSION_EXEMPT, True)
  120         return None
  121 
  122     def process_response(self, request, response):
  123         if self._is_exempt(request):
  124             return response
  125         return super(KolibriSessionMiddleware, self).process_response(request, response)
  126
  [end of kolibri/core/auth/middleware.py]
  </code>
  
  I need you to solve this issue by generating a single patch file that I can apply directly to this repository using git apply. Please respond with a single patch file in the following format.
  <patch>
  diff --git a/file.py b/file.py
  --- a/file.py
  +++ b/file.py
  @@ -1,27 +1,35 @@
   def euclidean(a, b):
  -    while b:
  -        a, b = b, a % b
  -    return a
  +    if b == 0:
  +        return a
  +    return euclidean(b, a % b)
   
   
   def bresenham(x0, y0, x1, y1):
       points = []
       dx = abs(x1 - x0)
       dy = abs(y1 - y0)
  -    sx = 1 if x0 < x1 else -1
  -    sy = 1 if y0 < y1 else -1
  -    err = dx - dy
  +    x, y = x0, y0
  +    sx = -1 if x0 > x1 else 1
  +    sy = -1 if y0 > y1 else 1
   
  -    while True:
  -        points.append((x0, y0))
  -        if x0 == x1 and y0 == y1:
  -            break
  -        e2 = 2 * err
  -        if e2 > -dy:
  -            err -= dy
  -            x0 += sx
  -        if e2 < dx:
  -            err += dx
  -            y0 += sy
  +    if dx > dy:
  +        err = dx / 2.0
  +        while x != x1:
  +            points.append((x, y))
  +            err -= dy
  +            if err < 0:
  +                y += sy
  +                err += dx
  +            x += sx
  +    else:
  +        err = dy / 2.0
  +        while y != y1:
  +            points.append((x, y))
  +            err -= dx
  +            if err < 0:
  +                x += sx
  +                err += dy
  +            y += sy
  +
  +    points.append((x, y))
       return points
  
  </patch>
golden_diff: diff --git a/kolibri/core/auth/middleware.py b/kolibri/core/auth/middleware.py
  --- a/kolibri/core/auth/middleware.py
  +++ b/kolibri/core/auth/middleware.py
  @@ -6,6 +6,7 @@
   from django.contrib.sessions.middleware import SessionMiddleware
   from django.core.cache import cache
   from django.core.exceptions import ImproperlyConfigured
  +from django.db.models.signals import post_save
   from django.utils.functional import SimpleLazyObject
   
   
  @@ -41,11 +42,14 @@
           )
   
   
  +USER_SESSION_CACHE_KEY = "USER_BY_SESSION_CACHE_{}"
  +
  +
   def _get_user(request):
       if not hasattr(request, "_cached_user"):
           try:
               user_id = _get_user_session_key(request)
  -            USER_CACHE_KEY = "USER_BY_SESSION_CACHE_{}".format(user_id)
  +            USER_CACHE_KEY = USER_SESSION_CACHE_KEY.format(user_id)
               user = cache.get(USER_CACHE_KEY)
               if not user:
                   user = get_user(request)
  @@ -60,6 +64,14 @@
       return request._cached_user
   
   
  +def clear_user_cache(sender, instance, created, **kwargs):
  +    if not created:
  +        cache.delete(USER_SESSION_CACHE_KEY.format(instance.id))
  +
  +
  +post_save.connect(clear_user_cache, sender=settings.AUTH_USER_MODEL)
  +
  +
   class CustomAuthenticationMiddleware(AuthenticationMiddleware):
       """
       Adaptation of Django's ``account.middleware.AuthenticationMiddleware``
  
verification_info: {"golden_diff": "diff --git a/kolibri/core/auth/middleware.py b/kolibri/core/auth/middleware.py\n--- a/kolibri/core/auth/middleware.py\n+++ b/kolibri/core/auth/middleware.py\n@@ -6,6 +6,7 @@\n from django.contrib.sessions.middleware import SessionMiddleware\n from django.core.cache import cache\n from django.core.exceptions import ImproperlyConfigured\n+from django.db.models.signals import post_save\n from django.utils.functional import SimpleLazyObject\n \n \n@@ -41,11 +42,14 @@\n         )\n \n \n+USER_SESSION_CACHE_KEY = \"USER_BY_SESSION_CACHE_{}\"\n+\n+\n def _get_user(request):\n     if not hasattr(request, \"_cached_user\"):\n         try:\n             user_id = _get_user_session_key(request)\n-            USER_CACHE_KEY = \"USER_BY_SESSION_CACHE_{}\".format(user_id)\n+            USER_CACHE_KEY = USER_SESSION_CACHE_KEY.format(user_id)\n             user = cache.get(USER_CACHE_KEY)\n             if not user:\n                 user = get_user(request)\n@@ -60,6 +64,14 @@\n     return request._cached_user\n \n \n+def clear_user_cache(sender, instance, created, **kwargs):\n+    if not created:\n+        cache.delete(USER_SESSION_CACHE_KEY.format(instance.id))\n+\n+\n+post_save.connect(clear_user_cache, sender=settings.AUTH_USER_MODEL)\n+\n+\n class CustomAuthenticationMiddleware(AuthenticationMiddleware):\n     \"\"\"\n     Adaptation of Django's ``account.middleware.AuthenticationMiddleware``\n", "issue": "Learner - Cannot change username and full name\n## Observed behavior\r\nThe fields for editing a learner's username and full name are disabled and I cannot change them.\r\n\r\n## Expected behavior\r\nIt should be possible for a learner to edit their username and full name if the options are enabled in Facility settings\r\n\r\n## Steps to reproduce the issue\r\n1. Install the following [build.](https://github.com/learningequality/kolibri/releases/tag/v0.15.0-beta2)\r\n2. Create a facility with enabled options for 'Allow learners to edit their username' and 'Allow learners to edit their full name' in Facility settings\r\n3. Create a Learner user\r\n4. Sign in with the Learner, go to Profile and select the Edit button.\r\n5. Attempt to edit the Full name and Username fields.\r\n\r\n## Additional information\r\n![2021-11-15_15-21-13](https://user-images.githubusercontent.com/79847249/141820419-c2511e0e-0ff2-475a-a500-9fdedfafa76a.png)\r\n\r\nLogs: \r\n[logs.zip](https://github.com/learningequality/kolibri/files/7540298/logs.zip)\r\n\r\n## Usage Details\r\n - OS: Windows 10\r\n - Browser: Chrome\n", "before_files": [{"content": "from django.apps import apps\nfrom django.conf import settings\nfrom django.contrib.auth import _get_user_session_key\nfrom django.contrib.auth import get_user\nfrom django.contrib.auth.middleware import AuthenticationMiddleware\nfrom django.contrib.sessions.middleware import SessionMiddleware\nfrom django.core.cache import cache\nfrom django.core.exceptions import ImproperlyConfigured\nfrom django.utils.functional import SimpleLazyObject\n\n\ndef get_anonymous_user_model():\n    \"\"\"\n    Return the Anonymous User model that is active in this project.\n    \"\"\"\n    try:\n        app_name = settings.AUTH_ANONYMOUS_USER_MODEL.split(\".\")[0]\n    except AttributeError:\n        raise ImproperlyConfigured(\"AUTH_ANONYMOUS_USER_MODEL is not a string\")\n    try:\n        model_name = settings.AUTH_ANONYMOUS_USER_MODEL.split(\".\")[1]\n        app = apps.get_app_config(app_name)\n        models_module = app.models_module\n    except IndexError:\n        raise ImproperlyConfigured(\n            \"AUTH_ANONYMOUS_USER_MODEL must be of the form 'app_label.model_name'\"\n        )\n    except LookupError:\n        raise ImproperlyConfigured(\n            \"AUTH_ANONYMOUS_USER_MODEL refers to an app '{}' that has not been installed\".format(\n                app_name\n            )\n        )\n    try:\n        return getattr(models_module, model_name)\n    except AttributeError:\n        raise ImproperlyConfigured(\n            \"AUTH_ANONYMOUS_USER_MODEL refers to a model '{}' that does not exist in the app '{}'\".format(\n                model_name, app_name\n            )\n        )\n\n\ndef _get_user(request):\n    if not hasattr(request, \"_cached_user\"):\n        try:\n            user_id = _get_user_session_key(request)\n            USER_CACHE_KEY = \"USER_BY_SESSION_CACHE_{}\".format(user_id)\n            user = cache.get(USER_CACHE_KEY)\n            if not user:\n                user = get_user(request)\n                cache.set(USER_CACHE_KEY, user)\n        except KeyError:\n            user = get_user(request)\n        if user.is_anonymous():\n            AnonymousUser = get_anonymous_user_model()\n            user = AnonymousUser()\n        request._cached_user = user\n\n    return request._cached_user\n\n\nclass CustomAuthenticationMiddleware(AuthenticationMiddleware):\n    \"\"\"\n    Adaptation of Django's ``account.middleware.AuthenticationMiddleware``\n    to replace the default AnonymousUser with a custom implementation.\n    \"\"\"\n\n    def process_request(self, request):\n        if not hasattr(request, \"session\"):\n            raise AssertionError(\n                \"The authentication middleware requires session middleware \"\n                \"to be installed. Edit your MIDDLEWARE_CLASSES setting to insert \"\n                \"'django.contrib.sessions.middleware.SessionMiddleware' before \"\n                \"'kolibri.core.auth.middleware.CustomAuthenticationMiddleware'.\"\n            )\n        request.user = SimpleLazyObject(lambda: _get_user(request))\n\n\nclass XhrPreventLoginPromptMiddleware(object):\n    \"\"\"\n    By default, HTTP 401 responses are sent with a ``WWW-Authenticate``\n    header. Web browsers react to this header by displaying a login prompt\n    dialog.  By removing the header, the login prompt can be avoided.  While\n    this isn't recommended in general, there's a convention of removing it\n    for XHR requests, so that unauthenticated XHR requests don't trigger a\n    popup.\n\n    See `here <https://stackoverflow.com/a/20221330>`_ for reference.\n    \"\"\"\n\n    def __init__(self, get_response):\n        self.get_response = get_response\n\n    def __call__(self, request):\n        response = self.get_response(request)\n        if response and response.status_code == 401 and request.is_ajax():\n            del response[\"WWW-Authenticate\"]\n        return response\n\n\nSESSION_EXEMPT = \"_session_exempt\"\n\n\ndef session_exempt(view):\n    def wrapper_func(*args, **kwargs):\n        return view(*args, **kwargs)\n\n    setattr(wrapper_func, SESSION_EXEMPT, True)\n    return wrapper_func\n\n\nclass KolibriSessionMiddleware(SessionMiddleware):\n    def _is_exempt(self, obj):\n        return hasattr(obj, SESSION_EXEMPT)\n\n    def process_view(self, request, callback, callback_args, callback_kwargs):\n        if self._is_exempt(callback):\n            setattr(request, SESSION_EXEMPT, True)\n        return None\n\n    def process_response(self, request, response):\n        if self._is_exempt(request):\n            return response\n        return super(KolibriSessionMiddleware, self).process_response(request, response)\n", "path": "kolibri/core/auth/middleware.py"}]}