Repository: pennersr/django-allauth
Pull request #3412 (2023-09-03 11:44:46)

refactor: Provide request context
The availability of `request` throughout the code is not consistent. Some methods get `request` passed explicitly, some don't, and some constructs have it available both as a parameter and as `self.request`. Having `request` available is essential, especially when trying to implement adapter hooks, though littering the code base with an explicit `request` is not very great.

With the changes made here, there is always a request available if you need it: `allauth.core.context.request`.



Patch:
diff --git a/ChangeLog.rst b/ChangeLog.rst
index 85212d9b..c444bae2 100644
--- a/ChangeLog.rst
+++ b/ChangeLog.rst
@@ -6,6 +6,16 @@ Note worthy changes
 
 - Added builtin support for Two-Factor Authentication via the ``allauth.mfa`` app.
 
+- The fact that ``request`` is not available globally has left its mark on the
+  code over the years. Some functions get explicitly passed a request, some do
+  not, and some constructs have it available both as a parameter and as
+  ``self.request``.  As having request available is essential, especially when
+  trying to implement adapter hooks, the request has now been made globally
+  available via::
+
+    from allauth.core import context
+    context.request
+
 
 Backwards incompatible changes
 ------------------------------
diff --git a/allauth/account/adapter.py b/allauth/account/adapter.py
index e58ef6d6..1bf7c9dd 100644
--- a/allauth/account/adapter.py
+++ b/allauth/account/adapter.py
@@ -31,6 +31,7 @@ from django.utils.translation import gettext_lazy as _
 from allauth import app_settings as allauth_app_settings, ratelimit
 from allauth.account import signals
 from allauth.account.app_settings import EmailVerificationMethod
+from allauth.core import context
 from allauth.utils import (
     build_absolute_uri,
     generate_unique_username,
@@ -57,7 +58,9 @@ class DefaultAccountAdapter(object):
     }
 
     def __init__(self, request=None):
-        self.request = request
+        # Explicitly passing `request` is deprecated, just use:
+        # `allauth.core.context.request`.
+        self.request = context.request
 
     def stash_verified_email(self, request, email):
         request.session["account_verified_email"] = email
@@ -88,7 +91,7 @@ class DefaultAccountAdapter(object):
     def format_email_subject(self, subject):
         prefix = app_settings.EMAIL_SUBJECT_PREFIX
         if prefix is None:
-            site = get_current_site(self.request)
+            site = get_current_site(context.request)
             prefix = "[{name}] ".format(name=site.name)
         return prefix + force_str(subject)
 
@@ -119,7 +122,7 @@ class DefaultAccountAdapter(object):
                 bodies[ext] = render_to_string(
                     template_name,
                     context,
-                    self.request,
+                    globals()["context"].request,
                 ).strip()
             except TemplateDoesNotExist:
                 if ext == "txt" and not bodies:
@@ -333,7 +336,7 @@ class DefaultAccountAdapter(object):
                 escaped_message = render_to_string(
                     message_template,
                     message_context,
-                    self.request,
+                    context.request,
                 ).strip()
                 if escaped_message:
                     message = html.unescape(escaped_message)
@@ -534,7 +537,7 @@ class DefaultAccountAdapter(object):
             )
 
         # get_host already validates the given host, so no need to check it again
-        allowed_hosts = {self.request.get_host()} | set(settings.ALLOWED_HOSTS)
+        allowed_hosts = {context.request.get_host()} | set(settings.ALLOWED_HOSTS)
 
         if "*" in allowed_hosts:
             parsed_host = urlparse(url).netloc
@@ -574,18 +577,18 @@ class DefaultAccountAdapter(object):
         return send_email
 
     def send_account_already_exists_mail(self, email):
-        signup_url = build_absolute_uri(self.request, reverse("account_signup"))
+        signup_url = build_absolute_uri(context.request, reverse("account_signup"))
         password_reset_url = build_absolute_uri(
-            self.request, reverse("account_reset_password")
+            context.request, reverse("account_reset_password")
         )
-        context = {
-            "request": self.request,
-            "current_site": get_current_site(self.request),
+        ctx = {
+            "request": context.request,
+            "current_site": get_current_site(context.request),
             "email": email,
             "signup_url": signup_url,
             "password_reset_url": password_reset_url,
         }
-        self.send_mail("account/email/account_already_exists", email, context)
+        self.send_mail("account/email/account_already_exists", email, ctx)
 
     def send_confirmation_mail(self, request, emailconfirmation, signup):
         current_site = get_current_site(request)
diff --git a/allauth/account/admin.py b/allauth/account/admin.py
index 3d79ee29..9ca3eb58 100644
--- a/allauth/account/admin.py
+++ b/allauth/account/admin.py
@@ -13,7 +13,7 @@ class EmailAddressAdmin(admin.ModelAdmin):
     actions = ["make_verified"]
 
     def get_search_fields(self, request):
-        base_fields = get_adapter(request).get_user_search_fields()
+        base_fields = get_adapter().get_user_search_fields()
         return ["email"] + list(map(lambda a: "user__" + a, base_fields))
 
     def make_verified(self, request, queryset):
diff --git a/allauth/account/forms.py b/allauth/account/forms.py
index 334a0cc9..fa44cf3f 100644
--- a/allauth/account/forms.py
+++ b/allauth/account/forms.py
@@ -380,7 +380,7 @@ class BaseSignupForm(_base_signup_form_class()):
             # Don't create a new acount, only send an email informing the user
             # that (s)he already has one...
             email = self.cleaned_data["email"]
-            adapter = get_adapter(request)
+            adapter = get_adapter()
             adapter.send_account_already_exists_mail(email)
             user = None
             resp = adapter.respond_email_verification_sent(request, None)
@@ -436,7 +436,7 @@ class SignupForm(BaseSignupForm):
     def save(self, request):
         if self.account_already_exists:
             raise ValueError(self.cleaned_data.get("email"))
-        adapter = get_adapter(request)
+        adapter = get_adapter()
         user = adapter.new_user(request)
         adapter.save_user(request, user, self)
         self.custom_signup(request, user)
@@ -569,7 +569,7 @@ class ResetPasswordForm(forms.Form):
             "request": request,
             "signup_url": signup_url,
         }
-        get_adapter(request).send_mail("account/email/unknown_account", email, context)
+        get_adapter().send_mail("account/email/unknown_account", email, context)
 
     def _send_password_reset_mail(self, request, email, users, **kwargs):
         token_generator = kwargs.get("token_generator", default_token_generator)
@@ -600,9 +600,7 @@ class ResetPasswordForm(forms.Form):
 
             if app_settings.AUTHENTICATION_METHOD != AuthenticationMethod.EMAIL:
                 context["username"] = user_username(user)
-            get_adapter(request).send_mail(
-                "account/email/password_reset_key", email, context
-            )
+            get_adapter().send_mail("account/email/password_reset_key", email, context)
 
 
 class ResetPasswordKeyForm(PasswordVerificationMixin, forms.Form):
diff --git a/allauth/account/middleware.py b/allauth/account/middleware.py
index d351882b..0abea807 100644
--- a/allauth/account/middleware.py
+++ b/allauth/account/middleware.py
@@ -1,5 +1,7 @@
 from django.conf import settings
 
+from allauth.core import context
+
 
 class AccountMiddleware:
     def __init__(self, get_response):
@@ -7,9 +9,10 @@ class AccountMiddleware:
         # One-time configuration and initialization.
 
     def __call__(self, request):
-        response = self.get_response(request)
-        self._remove_dangling_login(request, response)
-        return response
+        with context.request_context(request):
+            response = self.get_response(request)
+            self._remove_dangling_login(request, response)
+            return response
 
     def _remove_dangling_login(self, request, response):
         if request.path.startswith(settings.STATIC_URL):
diff --git a/allauth/account/models.py b/allauth/account/models.py
index 3b03f7d6..6489d7bf 100644
--- a/allauth/account/models.py
+++ b/allauth/account/models.py
@@ -113,7 +113,7 @@ class EmailConfirmationMixin:
     def confirm(self, request):
         email_address = self.email_address
         if not email_address.verified:
-            confirmed = get_adapter(request).confirm_email(request, email_address)
+            confirmed = get_adapter().confirm_email(request, email_address)
             if confirmed:
                 signals.email_confirmed.send(
                     sender=self.__class__,
@@ -123,7 +123,7 @@ class EmailConfirmationMixin:
                 return email_address
 
     def send(self, request=None, signup=False):
-        get_adapter(request).send_confirmation_mail(request, self, signup)
+        get_adapter().send_confirmation_mail(request, self, signup)
         signals.email_confirmation_sent.send(
             sender=self.__class__,
             request=request,
diff --git a/allauth/account/utils.py b/allauth/account/utils.py
index 26cc5eb8..ead96dd8 100644
--- a/allauth/account/utils.py
+++ b/allauth/account/utils.py
@@ -40,7 +40,7 @@ def get_next_redirect_url(request, redirect_field_name="next"):
     via the request.
     """
     redirect_to = get_request_param(request, redirect_field_name)
-    if not get_adapter(request).is_safe_url(redirect_to):
+    if not get_adapter().is_safe_url(redirect_to):
         redirect_to = None
     return redirect_to
 
@@ -55,9 +55,9 @@ def get_login_redirect_url(request, url=None, redirect_field_name="next", signup
         ret = get_next_redirect_url(request, redirect_field_name=redirect_field_name)
     if not ret:
         if signup:
-            ret = get_adapter(request).get_signup_redirect_url(request)
+            ret = get_adapter().get_signup_redirect_url(request)
         else:
-            ret = get_adapter(request).get_login_redirect_url(request)
+            ret = get_adapter().get_login_redirect_url(request)
     return ret
 
 
@@ -172,7 +172,7 @@ def _perform_login(request, login):
     # is_active, yet, adapter methods could toy with is_active in a
     # `user_signed_up` signal. Furthermore, social users should be
     # stopped anyway.
-    adapter = get_adapter(request)
+    adapter = get_adapter()
     try:
         hook_kwargs = _get_login_hook_kwargs(login)
         response = adapter.pre_login(request, login.user, **hook_kwargs)
@@ -200,7 +200,7 @@ def _get_login_hook_kwargs(login):
 def resume_login(request, login):
     from allauth.account.stages import LoginStageController
 
-    adapter = get_adapter(request)
+    adapter = get_adapter()
     ctrl = LoginStageController(request, login)
     try:
         response = ctrl.handle()
@@ -262,7 +262,7 @@ def cleanup_email_addresses(request, addresses):
     """
     from .models import EmailAddress
 
-    adapter = get_adapter(request)
+    adapter = get_adapter()
     # Let's group by `email`
     e2a = OrderedDict()  # maps email to EmailAddress
     primary_addresses = []
@@ -326,7 +326,7 @@ def setup_user_email(request, user, addresses):
     assert not EmailAddress.objects.filter(user=user).exists()
     priority_addresses = []
     # Is there a stashed email?
-    adapter = get_adapter(request)
+    adapter = get_adapter()
     stashed_email = adapter.unstash_verified_email(request)
     if stashed_email:
         priority_addresses.append(
@@ -364,7 +364,7 @@ def send_email_confirmation(request, user, signup=False, email=None):
     """
     from .models import EmailAddress
 
-    adapter = get_adapter(request)
+    adapter = get_adapter()
 
     if not email:
         email = user_email(user)
diff --git a/allauth/account/views.py b/allauth/account/views.py
index 9ae1085e..e7c7b80d 100644
--- a/allauth/account/views.py
+++ b/allauth/account/views.py
@@ -57,7 +57,7 @@ sensitive_post_parameters_m = method_decorator(
 
 
 def _ajax_response(request, response, form=None, data=None):
-    adapter = get_adapter(request)
+    adapter = get_adapter()
     if adapter.is_ajax(request):
         if isinstance(response, HttpResponseRedirect) or isinstance(
             response, HttpResponsePermanentRedirect
@@ -509,7 +509,7 @@ class EmailView(AjaxCapableProcessFormViewMixin, FormView):
         email_address = self._get_email_address(request)
         if email_address:
             if email_address.primary:
-                get_adapter(request).add_message(
+                get_adapter().add_message(
                     request,
                     messages.ERROR,
                     "account/messages/cannot_delete_primary_email.txt",
@@ -523,7 +523,7 @@ class EmailView(AjaxCapableProcessFormViewMixin, FormView):
                     user=request.user,
                     email_address=email_address,
                 )
-                get_adapter(request).add_message(
+                get_adapter().add_message(
                     request,
                     messages.SUCCESS,
                     "account/messages/email_deleted.txt",
@@ -544,7 +544,7 @@ class EmailView(AjaxCapableProcessFormViewMixin, FormView):
                     user=request.user, verified=True
                 ).exists()
             ):
-                get_adapter(request).add_message(
+                get_adapter().add_message(
                     request,
                     messages.ERROR,
                     "account/messages/unverified_primary_email.txt",
@@ -559,7 +559,7 @@ class EmailView(AjaxCapableProcessFormViewMixin, FormView):
                 except EmailAddress.DoesNotExist:
                     from_email_address = None
                 email_address.set_as_primary()
-                get_adapter(request).add_message(
+                get_adapter().add_message(
                     request,
                     messages.SUCCESS,
                     "account/messages/primary_email_set.txt",
diff --git a/allauth/core/__init__.py b/allauth/core/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/allauth/core/context.py b/allauth/core/context.py
new file mode 100644
index 00000000..1fba29bb
--- /dev/null
+++ b/allauth/core/context.py
@@ -0,0 +1,20 @@
+from contextlib import contextmanager
+from contextvars import ContextVar
+
+
+_request_var = ContextVar("request", default=None)
+
+
+def __getattr__(name):
+    if name == "request":
+        return _request_var.get()
+    raise AttributeError(name)
+
+
+@contextmanager
+def request_context(request):
+    token = _request_var.set(request)
+    try:
+        yield
+    finally:
+        _request_var.reset(token)
diff --git a/allauth/mfa/adapter.py b/allauth/mfa/adapter.py
index a15d89ac..bb6f1cf6 100644
--- a/allauth/mfa/adapter.py
+++ b/allauth/mfa/adapter.py
@@ -2,6 +2,7 @@ from django.utils.translation import gettext_lazy as _
 
 from allauth import app_settings as allauth_settings
 from allauth.account.utils import user_email, user_username
+from allauth.core import context
 from allauth.mfa import app_settings
 from allauth.utils import import_attribute
 
@@ -24,9 +25,6 @@ class DefaultMFAAdapter:
     }
     "The error messages that can occur as part of MFA form handling."
 
-    def __init__(self, request=None):
-        self.request = request
-
     def get_totp_label(self, user) -> str:
         """Returns the label used for representing the given user in a TOTP QR
         code.
@@ -47,9 +45,9 @@ class DefaultMFAAdapter:
             if allauth_settings.SITES_ENABLED:
                 from django.contrib.sites.models import Site
 
-                issuer = Site.objects.get_current(self.request).name
+                issuer = Site.objects.get_current(context.request).name
             else:
-                issuer = self.request.get_host()
+                issuer = context.request.get_host()
         return issuer
 
     def encrypt(self, text: str) -> str:
@@ -65,5 +63,5 @@ class DefaultMFAAdapter:
         return text
 
 
-def get_adapter(request=None):
-    return import_attribute(app_settings.ADAPTER)(request)
+def get_adapter():
+    return import_attribute(app_settings.ADAPTER)()
diff --git a/allauth/mfa/views.py b/allauth/mfa/views.py
index 070af1b8..d6ba1c71 100644
--- a/allauth/mfa/views.py
+++ b/allauth/mfa/views.py
@@ -75,7 +75,7 @@ class ActivateTOTPView(FormView):
 
     def get_context_data(self, **kwargs):
         ret = super().get_context_data(**kwargs)
-        adapter = get_adapter(self.request)
+        adapter = get_adapter()
         totp_url = totp.build_totp_url(
             adapter.get_totp_label(self.request.user),
             adapter.get_totp_issuer(),
diff --git a/allauth/socialaccount/adapter.py b/allauth/socialaccount/adapter.py
index bf215cf5..989dcf12 100644
--- a/allauth/socialaccount/adapter.py
+++ b/allauth/socialaccount/adapter.py
@@ -9,6 +9,8 @@ from django.db.models import Q
 from django.urls import reverse
 from django.utils.translation import gettext_lazy as _
 
+from allauth.core import context
+
 from ..account.adapter import get_adapter as get_account_adapter
 from ..account.app_settings import EmailVerificationMethod
 from ..account.models import EmailAddress
@@ -32,7 +34,9 @@ class DefaultSocialAccountAdapter(object):
     }
 
     def __init__(self, request=None):
-        self.request = request
+        # Explicitly passing `request` is deprecated, just use:
+        # `allauth.core.context.request`.
+        self.request = context.request
 
     def pre_social_login(self, request, sociallogin):
         """
diff --git a/allauth/socialaccount/forms.py b/allauth/socialaccount/forms.py
index 27a5e549..40cbe1a1 100644
--- a/allauth/socialaccount/forms.py
+++ b/allauth/socialaccount/forms.py
@@ -24,7 +24,7 @@ class SignupForm(BaseSignupForm):
         super(SignupForm, self).__init__(*args, **kwargs)
 
     def save(self, request):
-        adapter = get_adapter(request)
+        adapter = get_adapter()
         user = adapter.save_user(request, self.sociallogin, form=self)
         self.custom_signup(request, user)
         return user
diff --git a/allauth/socialaccount/helpers.py b/allauth/socialaccount/helpers.py
index c983c92a..227e4477 100644
--- a/allauth/socialaccount/helpers.py
+++ b/allauth/socialaccount/helpers.py
@@ -23,7 +23,7 @@ from .providers.base import AuthError, AuthProcess
 
 
 def _process_auto_signup(request, sociallogin):
-    auto_signup = get_adapter(request).is_auto_signup_allowed(request, sociallogin)
+    auto_signup = get_adapter().is_auto_signup_allowed(request, sociallogin)
     if not auto_signup:
         return False, None
     email = user_email(sociallogin.user)
@@ -83,12 +83,12 @@ def _process_signup(request, sociallogin):
         # TODO: This part contains a lot of duplication of logic
         # ("closed" rendering, create user, send email, in active
         # etc..)
-        if not get_adapter(request).is_open_for_signup(request, sociallogin):
+        if not get_adapter().is_open_for_signup(request, sociallogin):
             return render(
                 request,
                 "account/signup_closed." + account_settings.TEMPLATE_EXTENSION,
             )
-        get_adapter(request).save_user(request, sociallogin, form=None)
+        get_adapter().save_user(request, sociallogin, form=None)
         resp = complete_social_signup(request, sociallogin)
     return resp
 
@@ -113,7 +113,7 @@ def render_authentication_error(
     try:
         if extra_context is None:
             extra_context = {}
-        get_adapter(request).authentication_error(
+        get_adapter().authentication_error(
             request,
             provider_id,
             error=error,
@@ -143,7 +143,7 @@ def _add_social_account(request, sociallogin):
     if request.user.is_anonymous:
         # This should not happen. Simply redirect to the connections
         # view (which has a login required)
-        connect_redirect_url = get_adapter(request).get_connect_redirect_url(
+        connect_redirect_url = get_adapter().get_connect_redirect_url(
             request, sociallogin.account
         )
         return HttpResponseRedirect(connect_redirect_url)
@@ -174,9 +174,7 @@ def _add_social_account(request, sociallogin):
             sender=SocialLogin, request=request, sociallogin=sociallogin
         )
     assert request.user.is_authenticated
-    default_next = get_adapter(request).get_connect_redirect_url(
-        request, sociallogin.account
-    )
+    default_next = get_adapter().get_connect_redirect_url(request, sociallogin.account)
     next_url = sociallogin.get_redirect_url(request) or default_next
     get_account_adapter(request).add_message(
         request,
@@ -191,7 +189,7 @@ def complete_social_login(request, sociallogin):
     assert not sociallogin.is_existing
     sociallogin.lookup()
     try:
-        get_adapter(request).pre_social_login(request, sociallogin)
+        get_adapter().pre_social_login(request, sociallogin)
         signals.pre_social_login.send(
             sender=SocialLogin, request=request, sociallogin=sociallogin
         )
diff --git a/allauth/socialaccount/models.py b/allauth/socialaccount/models.py
index e12374fe..cf51e8e8 100644
--- a/allauth/socialaccount/models.py
+++ b/allauth/socialaccount/models.py
@@ -141,7 +141,7 @@ class SocialAccount(models.Model):
         provider = getattr(self, "_provider", None)
         if provider:
             return provider
-        adapter = get_adapter(request)
+        adapter = get_adapter()
         provider = self._provider = adapter.get_provider(
             request, provider=self.provider
         )
diff --git a/allauth/socialaccount/providers/base/provider.py b/allauth/socialaccount/providers/base/provider.py
index 019f70c2..68b11c2f 100644
--- a/allauth/socialaccount/providers/base/provider.py
+++ b/allauth/socialaccount/providers/base/provider.py
@@ -60,7 +60,7 @@ class Provider(object):
         from allauth.socialaccount.adapter import get_adapter
         from allauth.socialaccount.models import SocialAccount, SocialLogin
 
-        adapter = get_adapter(request)
+        adapter = get_adapter()
         uid = self.extract_uid(response)
         if not isinstance(uid, str):
             raise ValueError(f"uid must be a string: {repr(uid)}")
diff --git a/allauth/socialaccount/providers/draugiem/views.py b/allauth/socialaccount/providers/draugiem/views.py
index 55c6924e..65651ca6 100644
--- a/allauth/socialaccount/providers/draugiem/views.py
+++ b/allauth/socialaccount/providers/draugiem/views.py
@@ -26,7 +26,7 @@ AUTHORIZE_URL = "http://api.draugiem.lv/authorize"
 
 
 def login(request):
-    app = get_adapter(request).get_app(request, DraugiemProvider.id)
+    app = get_adapter().get_app(request, DraugiemProvider.id)
     redirect_url = request.build_absolute_uri(reverse(callback))
     redirect_url_hash = md5((app.secret + redirect_url).encode("utf-8")).hexdigest()
     params = {
@@ -58,7 +58,7 @@ def callback(request):
     ret = None
     auth_exception = None
     try:
-        app = get_adapter(request).get_app(request, DraugiemProvider.id)
+        app = get_adapter().get_app(request, DraugiemProvider.id)
         login = draugiem_complete_login(request, app, request.GET["dr_auth_code"])
         login.state = SocialLogin.unstash_state(request)
 
@@ -75,7 +75,7 @@ def callback(request):
 
 
 def draugiem_complete_login(request, app, code):
-    provider = get_adapter(request).get_provider(request, DraugiemProvider.id)
+    provider = get_adapter().get_provider(request, DraugiemProvider.id)
     response = requests.get(
         ACCESS_TOKEN_URL,
         {"action": "authorize", "app": app.secret, "code": code},
diff --git a/allauth/socialaccount/providers/facebook/views.py b/allauth/socialaccount/providers/facebook/views.py
index 1cdc84ea..51402227 100644
--- a/allauth/socialaccount/providers/facebook/views.py
+++ b/allauth/socialaccount/providers/facebook/views.py
@@ -79,7 +79,7 @@ def login_by_token(request):
         form = FacebookConnectForm(request.POST)
         if form.is_valid():
             try:
-                adapter = get_adapter(request)
+                adapter = get_adapter()
                 provider = adapter.get_provider(request, FacebookProvider.id)
                 login_options = provider.get_fb_login_options(request)
                 app = provider.app
diff --git a/allauth/socialaccount/providers/saml/utils.py b/allauth/socialaccount/providers/saml/utils.py
index 7376f91e..8c72a40f 100644
--- a/allauth/socialaccount/providers/saml/utils.py
+++ b/allauth/socialaccount/providers/saml/utils.py
@@ -13,7 +13,7 @@ from .provider import SAMLProvider
 
 
 def get_app_or_404(request, organization_slug):
-    adapter = get_adapter(request)
+    adapter = get_adapter()
     try:
         return adapter.get_app(
             request, provider=SAMLProvider.id, client_id=organization_slug
diff --git a/allauth/socialaccount/providers/steam/provider.py b/allauth/socialaccount/providers/steam/provider.py
index c568d558..144d8761 100644
--- a/allauth/socialaccount/providers/steam/provider.py
+++ b/allauth/socialaccount/providers/steam/provider.py
@@ -59,7 +59,7 @@ class SteamOpenIDProvider(OpenIDProvider):
 
     def __init__(self, request, app=None):
         if app is None:
-            app = get_adapter(request).get_app(request, self.id)
+            app = get_adapter().get_app(request, self.id)
         super().__init__(request, app=app)
 
     def get_login_url(self, request, **kwargs):
diff --git a/allauth/socialaccount/providers/telegram/views.py b/allauth/socialaccount/providers/telegram/views.py
index 5ee7fe35..90fa6fae 100644
--- a/allauth/socialaccount/providers/telegram/views.py
+++ b/allauth/socialaccount/providers/telegram/views.py
@@ -23,7 +23,7 @@ from .provider import TelegramProvider
 
 class LoginView(View):
     def dispatch(self, request):
-        provider = get_adapter(request).get_provider(request, TelegramProvider.id)
+        provider = get_adapter().get_provider(request, TelegramProvider.id)
         return_to = request.build_absolute_uri(
             reverse("telegram_callback") + "?" + request.GET.urlencode()
         )
@@ -52,7 +52,7 @@ class CallbackView(View):
         result = request.POST.get("tgAuthResult")
         padding = "=" * (4 - (len(result) % 4))
         data = json.loads(base64.b64decode(result + padding))
-        adapter = get_adapter(request)
+        adapter = get_adapter()
         provider = adapter.get_provider(request, TelegramProvider.id)
         hash = data.pop("hash")
         payload = "\n".join(sorted(["{}={}".format(k, v) for k, v in data.items()]))
diff --git a/allauth/socialaccount/templatetags/socialaccount.py b/allauth/socialaccount/templatetags/socialaccount.py
index 105558cc..928544b6 100644
--- a/allauth/socialaccount/templatetags/socialaccount.py
+++ b/allauth/socialaccount/templatetags/socialaccount.py
@@ -16,7 +16,7 @@ def provider_login_url(context, provider, **params):
     """
     request = context.get("request")
     if isinstance(provider, str):
-        adapter = get_adapter(request)
+        adapter = get_adapter()
         provider = adapter.get_provider(request, provider)
     query = dict(params)
     auth_params = query.get("auth_params", None)
@@ -42,7 +42,7 @@ def provider_login_url(context, provider, **params):
 @register.simple_tag(takes_context=True)
 def providers_media_js(context):
     request = context["request"]
-    providers = get_adapter(request).list_providers(request)
+    providers = get_adapter().list_providers(request)
     ret = "\n".join(p.media_js(request) for p in providers)
     return mark_safe(ret)
 
@@ -75,6 +75,6 @@ def get_providers(context):
     a list of social providers configured for the current site.
     """
     request = context["request"]
-    adapter = get_adapter(request)
+    adapter = get_adapter()
     providers = adapter.list_providers(request)
     return sorted(providers, key=lambda p: p.name)


Test patch:
diff --git a/allauth/account/tests/test_signup.py b/allauth/account/tests/test_signup.py
index d1d7e83b..ed7c5933 100644
--- a/allauth/account/tests/test_signup.py
+++ b/allauth/account/tests/test_signup.py
@@ -14,6 +14,7 @@ from allauth.account import app_settings
 from allauth.account.adapter import get_adapter
 from allauth.account.forms import BaseSignupForm, SignupForm
 from allauth.account.models import EmailAddress
+from allauth.core import context
 from allauth.tests import TestCase
 from allauth.utils import get_user_model, get_username_max_length
 
@@ -218,7 +219,8 @@ class SignupTests(TestCase):
         request.session["account_verified_email"] = verified_email
         from allauth.account.views import signup
 
-        resp = signup(request)
+        with context.request_context(request):
+            resp = signup(request)
         self.assertEqual(resp.status_code, 302)
         self.assertEqual(
             resp["location"], get_adapter().get_signup_redirect_url(request)
@@ -273,7 +275,8 @@ class SignupTests(TestCase):
         request.user = AnonymousUser()
         from allauth.account.views import signup
 
-        signup(request)
+        with context.request_context(request):
+            signup(request)
         user = get_user_model().objects.get(username="johndoe")
         self.assertEqual(user.email, "john@example.org")
 
diff --git a/allauth/account/tests/test_utils.py b/allauth/account/tests/test_utils.py
index 526b4139..6d2b5acc 100644
--- a/allauth/account/tests/test_utils.py
+++ b/allauth/account/tests/test_utils.py
@@ -21,6 +21,7 @@ from allauth.account.utils import (
     user_pk_to_url_str,
     user_username,
 )
+from allauth.core import context
 from allauth.tests import TestCase, patch
 from allauth.utils import get_user_model
 
@@ -111,17 +112,17 @@ class UtilsTests(TestCase):
 
     @override_settings(ALLOWED_HOSTS=["allowed_host", "testserver"])
     def test_is_safe_url_no_wildcard(self):
-        request = RequestFactory().get("/")
-        self.assertTrue(get_adapter(request).is_safe_url("http://allowed_host/"))
-        self.assertFalse(get_adapter(request).is_safe_url("http://other_host/"))
+        with context.request_context(RequestFactory().get("/")):
+            self.assertTrue(get_adapter().is_safe_url("http://allowed_host/"))
+            self.assertFalse(get_adapter().is_safe_url("http://other_host/"))
 
     @override_settings(ALLOWED_HOSTS=["*"])
     def test_is_safe_url_wildcard(self):
-        request = RequestFactory().get("/")
-        self.assertTrue(get_adapter(request).is_safe_url("http://foobar.com/"))
-        self.assertTrue(get_adapter(request).is_safe_url("http://other_host/"))
+        with context.request_context(RequestFactory().get("/")):
+            self.assertTrue(get_adapter().is_safe_url("http://foobar.com/"))
+            self.assertTrue(get_adapter().is_safe_url("http://other_host/"))
 
     @override_settings(ALLOWED_HOSTS=["allowed_host", "testserver"])
     def test_is_safe_url_relative_path(self):
-        request = RequestFactory().get("/")
-        self.assertTrue(get_adapter(request).is_safe_url("/foo/bar"))
+        with context.request_context(RequestFactory().get("/")):
+            self.assertTrue(get_adapter().is_safe_url("/foo/bar"))
diff --git a/allauth/conftest.py b/allauth/conftest.py
index ea81f289..a24d4d19 100644
--- a/allauth/conftest.py
+++ b/allauth/conftest.py
@@ -6,6 +6,7 @@ import pytest
 
 from allauth.account.models import EmailAddress
 from allauth.account.utils import user_email, user_username
+from allauth.core import context
 from allauth.utils import get_user_model
 
 
@@ -72,3 +73,8 @@ def reauthentication_bypass():
             yield
 
     return f
+
+
+@pytest.fixture(autouse=True)
+def clear_context_request():
+    context._request_var.set(None)
diff --git a/allauth/socialaccount/providers/google/tests.py b/allauth/socialaccount/providers/google/tests.py
index d0d655d3..1d49f73e 100644
--- a/allauth/socialaccount/providers/google/tests.py
+++ b/allauth/socialaccount/providers/google/tests.py
@@ -141,7 +141,7 @@ class GoogleTests(OAuth2TestsMixin, TestCase):
         self.client.cookies[settings.SESSION_COOKIE_NAME] = store.session_key
         request = RequestFactory().get("/")
         request.session = self.client.session
-        adapter = get_adapter(request)
+        adapter = get_adapter()
         adapter.stash_verified_email(request, self.email)
         request.session.save()
 
diff --git a/allauth/socialaccount/tests/__init__.py b/allauth/socialaccount/tests/__init__.py
index c9275973..c9f70193 100644
--- a/allauth/socialaccount/tests/__init__.py
+++ b/allauth/socialaccount/tests/__init__.py
@@ -30,7 +30,7 @@ from allauth.utils import get_user_model
 
 def setup_app(provider_id):
     request = RequestFactory().get("/")
-    apps = get_adapter(request).list_apps(request, provider_id)
+    apps = get_adapter().list_apps(request, provider_id)
     if apps:
         return apps[0]
 
diff --git a/allauth/socialaccount/tests/test_login.py b/allauth/socialaccount/tests/test_login.py
index 552cb317..9b632b56 100644
--- a/allauth/socialaccount/tests/test_login.py
+++ b/allauth/socialaccount/tests/test_login.py
@@ -7,6 +7,7 @@ from django.urls import reverse
 
 import pytest
 
+from allauth.core import context
 from allauth.socialaccount.helpers import complete_social_login
 from allauth.socialaccount.models import SocialAccount
 
@@ -44,8 +45,8 @@ def test_email_authentication(
     SessionMiddleware(lambda request: None).process_request(request)
     MessageMiddleware(lambda request: None).process_request(request)
     request.user = AnonymousUser()
-
-    resp = complete_social_login(request, sociallogin)
+    with context.request_context(request):
+        resp = complete_social_login(request, sociallogin)
     if setting == "off":
         assert resp["location"] == reverse("account_email_verification_sent")
     else:
diff --git a/allauth/socialaccount/tests/test_signup.py b/allauth/socialaccount/tests/test_signup.py
index 40029cd8..ccba05c1 100644
--- a/allauth/socialaccount/tests/test_signup.py
+++ b/allauth/socialaccount/tests/test_signup.py
@@ -10,6 +10,7 @@ import allauth.app_settings
 from allauth.account import app_settings as account_settings
 from allauth.account.models import EmailAddress
 from allauth.account.utils import user_email, user_username
+from allauth.core import context
 from allauth.socialaccount import providers
 from allauth.socialaccount.helpers import complete_social_login
 from allauth.socialaccount.models import SocialAccount, SocialApp, SocialLogin
@@ -56,7 +57,8 @@ class SignupTests(TestCase):
 
         account = SocialAccount(provider="openid", uid="123")
         sociallogin = SocialLogin(user=user, account=account)
-        complete_social_login(request, sociallogin)
+        with context.request_context(request):
+            complete_social_login(request, sociallogin)
 
         user = User.objects.get(**{account_settings.USER_MODEL_USERNAME_FIELD: "test"})
         self.assertTrue(
@@ -81,7 +83,8 @@ class SignupTests(TestCase):
         # POST different username/email to social signup form
         request.method = "POST"
         request.POST = {"username": "other", "email": "other@example.com"}
-        resp = signup(request)
+        with context.request_context(request):
+            resp = signup(request)
         self.assertEqual(resp["location"], "/accounts/profile/")
         user = get_user_model().objects.get(
             **{account_settings.USER_MODEL_EMAIL_FIELD: "other@example.com"}
@@ -103,7 +106,8 @@ class SignupTests(TestCase):
         # POST email to social signup form (username not present)
         request.method = "POST"
         request.POST = {"email": "other@example.com"}
-        resp = signup(request)
+        with context.request_context(request):
+            resp = signup(request)
         self.assertEqual(resp["location"], "/accounts/profile/")
         user = get_user_model().objects.get(
             **{account_settings.USER_MODEL_EMAIL_FIELD: "other@example.com"}
@@ -152,7 +156,8 @@ class SignupTests(TestCase):
 
         account = SocialAccount(provider="twitter", uid="123")
         sociallogin = SocialLogin(user=user, account=account)
-        complete_social_login(request, sociallogin)
+        with context.request_context(request):
+            complete_social_login(request, sociallogin)
 
         self.assertNotIn(request.user.username, account_settings.USERNAME_BLACKLIST)
 
@@ -181,7 +186,8 @@ class SignupTests(TestCase):
         request.user = AnonymousUser()
         SessionMiddleware(lambda request: None).process_request(request)
         MessageMiddleware(lambda request: None).process_request(request)
-        resp = complete_social_login(request, sociallogin)
+        with context.request_context(request):
+            resp = complete_social_login(request, sociallogin)
         return request, resp
 
     def test_disconnect(self):