from importlib import import_module
from allauth.account.adapter import get_adapter
from allauth.account.models import EmailAddress
from django.contrib.auth import get_user_model
from django.utils.decorators import method_decorator
from django.views.decorators.debug import sensitive_post_parameters
from django.conf import settings
from django_user_agents.utils import get_user_agent as get_user_agent_django
from rest_framework_simplejwt.exceptions import InvalidToken
from six import string_types
from jwt_allauth.constants import TEMPLATE_PATHS
from jwt_allauth.exceptions import NotVerifiedEmail, IncorrectCredentials
[docs]
def import_callable(path_or_callable):
"""
Convert a Python path string to a callable object or return the input if already callable.
Args:
path_or_callable (str|callable): Either a Python path string (module.attribute)
or an already callable object
Returns:
callable: The resolved callable object
Raises:
AssertionError: If input is string but not valid Python path
"""
if hasattr(path_or_callable, '__call__'):
return path_or_callable
else:
assert isinstance(path_or_callable, string_types)
package, attr = path_or_callable.rsplit('.', 1)
return getattr(import_module(package), attr)
[docs]
def get_client_ip(request):
"""
Extract client IP address from request metadata.
Priority:
1. X-Forwarded-For header (first entry if multiple)
2. REMOTE_ADDR meta value
Args:
request (HttpRequest): Django request object
Returns:
str: Client IP address or None if not found
"""
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0]
else:
ip = request.META.get('REMOTE_ADDR')
return ip
[docs]
def get_user_agent(f):
"""
Decorator that adds user agent and IP information to the request object.
Stores:
- user_agent: Parsed user agent details
- ip: Client IP address
Args:
f (function): View method to decorate
Returns:
function: Decorated view method
"""
def user_agent(self, request, *args, **kwargs):
if getattr(settings, 'JWT_ALLAUTH_COLLECT_USER_AGENT', False):
request.user_agent = get_user_agent_django(request)
request.ip = get_client_ip(request)
else:
request.user_agent = None
request.ip = None
return f(self, request, *args, **kwargs)
return user_agent
[docs]
def user_agent_dict(request):
"""
Generate a detailed dictionary of user agent information.
Includes:
- Browser details (name, version)
- OS details (name, version)
- Device information (family, brand, model)
- Network information (IP address)
- Device type flags (mobile, tablet, PC, bot)
Args:
request (HttpRequest): Django request object
Returns:
dict: Structured user agent details. Empty dict if no request.
"""
if request is None:
return {}
if request.user_agent is None:
return {}
return {
'browser': request.user_agent.browser.family,
'browser_version': request.user_agent.browser.version_string,
'os': request.user_agent.os.family,
'os_version': request.user_agent.os.version_string,
'device': request.user_agent.device.family,
'device_brand': request.user_agent.device.brand,
'device_model': request.user_agent.device.model,
'ip': request.ip,
'is_mobile': request.user_agent.is_mobile,
'is_tablet': request.user_agent.is_tablet,
'is_pc': request.user_agent.is_pc,
'is_bot': request.user_agent.is_bot,
}
sensitive_post_parameters_m = method_decorator(
sensitive_post_parameters(
'password', 'old_password', 'new_password1', 'new_password2', 'password1', 'password2'
)
)
[docs]
def get_template_path(constant, default):
"""
Get template path from settings using TEMPLATE_PATHS configuration.
Args:
constant (str): Key to look up in TEMPLATE_PATHS setting
default (str): Default path if not found in settings
Returns:
str: Configured template path or default value
"""
templates_path_dict = getattr(settings, TEMPLATE_PATHS, {})
return getattr(templates_path_dict, constant, default)
[docs]
def is_email_verified(user, raise_exception=False):
"""
Check if user has a verified email address.
Args:
user (User): User object to check
raise_exception (bool): Whether to raise NotVerifiedEmail if unverified
Returns:
bool: True if verified, False otherwise
Raises:
NotVerifiedEmail: If raise_exception=True and email is unverified
"""
if not EmailAddress.objects.filter(user=user.id, verified=True).exists():
if raise_exception:
raise NotVerifiedEmail()
return False
return True
[docs]
def allauth_authenticate(**kwargs):
"""
Authenticate user using allauth's adapter with enhanced verification.
Args:
**kwargs: Authentication credentials (typically username/email + password)
Returns:
User: Authenticated user object
Raises:
IncorrectCredentials: If authentication fails
NotVerifiedEmail: If email is not verified
"""
user = get_adapter().authenticate(**kwargs)
if user is None:
raise IncorrectCredentials()
is_email_verified(user, raise_exception=True)
return user
[docs]
def load_user(f):
"""
Decorator that loads the complete user object from the database for stateless JWT authentication.
This is necessary because JWT tokens only contain the user ID, and the full user object
might be needed in the view methods.
Usage:
.. code-block:: python
@load_user
def my_view_method(self, *args, **kwargs):
# self.request.user will be the complete user object
pass
"""
def wrapper(self, *args, **kwargs):
try:
self.request.user = get_user_model().objects.get(id=self.request.user.id)
except get_user_model().DoesNotExist:
raise InvalidToken()
res = f(self, *args, **kwargs)
return res
return wrapper