Django allauth 序列化错误自定义用户模型与 TimeZoneField
Django allauth Serialization error custom User model with TimeZoneField
我的自定义用户模型有一个 TimeZoneField:
from timezone_field import TimeZoneField
class User(AbstractBaseUser, PermissionsMixin):
class Meta:
verbose_name = _('user')
verbose_name_plural = _('users')
email = models.EmailField(_('email address'), unique=True, blank=False, null=False)
username = models.CharField(_('user name'), max_length=128, unique=True, blank=False, null=False)
is_staff = models.BooleanField(
_('staff status'),
default=False,
help_text=_('Designates whether the user can log into this admin site.'))
is_active = models.BooleanField(
_('active'),
default=True,
help_text=_(
'Designates whether this user should be treated as active. '
'Unselect this instead of deleting accounts.'))
date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
language = models.CharField(_('Language'), choices=settings.LANGUAGES, default=settings.ENGLISH, max_length=2)
timezone = TimeZoneField(verbose_name=_('Timezone'), default='Europe/London')
objects = UserManager()
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email']
我使用 django-allauth 通过 Google 帐户进行注册。当现有用户(之前通过 google 电子邮件注册,而不是 Google 帐户)尝试通过 Google 帐户登录时,我们遇到错误:
<DstTzInfo 'Europe/London' LMT-1 day, 23:59:00 STD> is not JSON serializable
Traceback:
File "/webapps/myproject/tmp/venv/lib/python3.4/site-packages/django/core/handlers/base.py" in get_response
149. response = self.process_exception_by_middleware(e, request)
File "/webapps/myproject/tmp/venv/lib/python3.4/site-packages/django/core/handlers/base.py" in get_response
147. response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/webapps/myproject/tmp/venv/lib/python3.4/site-packages/allauth/socialaccount/providers/oauth2/views.py" in view
55. return self.dispatch(request, *args, **kwargs)
File "/webapps/myproject/tmp/venv/lib/python3.4/site-packages/allauth/socialaccount/providers/oauth2/views.py" in dispatch
125. return complete_social_login(request, login)
File "/webapps/myproject/tmp/venv/lib/python3.4/site-packages/allauth/socialaccount/helpers.py" in complete_social_login
142. return _complete_social_login(request, sociallogin)
File "/webapps/myproject/tmp/venv/lib/python3.4/site-packages/allauth/socialaccount/helpers.py" in _complete_social_login
158. ret = _process_signup(request, sociallogin)
File "/webapps/myproject/tmp/venv/lib/python3.4/site-packages/allauth/socialaccount/helpers.py" in _process_signup
25. request.session['socialaccount_sociallogin'] = sociallogin.serialize()
File "/webapps/myproject/tmp/venv/lib/python3.4/site-packages/allauth/socialaccount/models.py" in serialize
189. user=serialize_instance(self.user),
File "/webapps/myproject/tmp/venv/lib/python3.4/site-packages/allauth/utils.py" in serialize_instance
194. return json.loads(json.dumps(data, cls=DjangoJSONEncoder))
File "/usr/lib/python3.4/json/__init__.py" in dumps
237. **kw).encode(obj)
File "/usr/lib/python3.4/json/encoder.py" in encode
192. chunks = self.iterencode(o, _one_shot=True)
File "/usr/lib/python3.4/json/encoder.py" in iterencode
250. return _iterencode(o, 0)
File "/webapps/myproject/tmp/venv/lib/python3.4/site-packages/django/core/serializers/json.py" in default
115. return super(DjangoJSONEncoder, self).default(o)
File "/usr/lib/python3.4/json/encoder.py" in default
173. raise TypeError(repr(o) + " is not JSON serializable")
Exception Type: TypeError at /accounts/google/login/callback/
Exception Value: <DstTzInfo 'Europe/London' LMT-1 day, 23:59:00 STD> is not JSON serializable
在 allauth 中序列化自定义字段有哪些方法?
我的解决方案是替换默认的 DefaultSocialAccountAdapter 和扩展 serialize_instance(来自 allauth.utils)以序列化 TimeZoneField。不要忘记在项目设置中设置自定义 adapret:
SOCIALACCOUNT_ADAPTER = 'myapp.adapter.MySocialAccountAdapter'
我还用直接帐户(通过电子邮件注册)替换了关联社交帐户的 pre_social_login(感谢 elssar 的示例:)
myapp.adapter.py:
import json
import base64
import logging
from django.db.models import FieldDoesNotExist, FileField
from django.db.models.fields import (BinaryField)
from django.utils import six
from django.core.serializers.json import DjangoJSONEncoder
from django.shortcuts import HttpResponse
try:
from django.utils.encoding import force_text
except ImportError:
from django.utils.encoding import force_unicode as force_text
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
from allauth.account.adapter import DefaultAccountAdapter
from allauth.utils import SERIALIZED_DB_FIELD_PREFIX
from allauth.exceptions import ImmediateHttpResponse
from timezone_field import TimeZoneField
from accounts.models import User
logger = logging.getLogger("django")
def my_serialize_instance(instance):
"""Instance serializer supported of serialization of TimeZoneField.
:param instance:
:return:
"""
data = {}
for k, v in instance.__dict__.items():
if k.startswith('_') or callable(v):
continue
try:
field = instance._meta.get_field(k)
if isinstance(field, BinaryField):
v = force_text(base64.b64encode(v))
elif isinstance(field, FileField):
if not isinstance(v, six.string_types):
v = v.name
elif isinstance(field, TimeZoneField):
v = six.text_type(v.zone)
# Check if the field is serializable. If not, we'll fall back
# to serializing the DB values which should cover most use cases.
try:
json.dumps(v, cls=DjangoJSONEncoder)
except TypeError:
v = field.get_prep_value(v)
k = SERIALIZED_DB_FIELD_PREFIX + k
except FieldDoesNotExist:
pass
data[k] = v
return json.loads(json.dumps(data, cls=DjangoJSONEncoder))
class MySocialAccountAdapter(DefaultSocialAccountAdapter):
"""Custom SocialAccountAdapter for django-allauth.
Replaced standard behavior for serialization of TimeZoneField.
Need set it in project settings:
SOCIALACCOUNT_ADAPTER = 'myapp.adapter.MySocialAccountAdapter'
"""
def __init__(self, request=None):
super(MySocialAccountAdapter, self).__init__(request=request)
def pre_social_login(self, request, sociallogin):
# This isn't tested, but should work
try:
emails = [email.email for email in sociallogin.email_addresses]
user = User.objects.get(email__in=emails)
sociallogin.connect(request, user)
raise ImmediateHttpResponse(response=HttpResponse())
except User.DoesNotExist:
pass
except Exception as ex:
logger.error(ex)
def serialize_instance(self, instance):
return my_serialize_instance(instance)
我的自定义用户模型有一个 TimeZoneField:
from timezone_field import TimeZoneField
class User(AbstractBaseUser, PermissionsMixin):
class Meta:
verbose_name = _('user')
verbose_name_plural = _('users')
email = models.EmailField(_('email address'), unique=True, blank=False, null=False)
username = models.CharField(_('user name'), max_length=128, unique=True, blank=False, null=False)
is_staff = models.BooleanField(
_('staff status'),
default=False,
help_text=_('Designates whether the user can log into this admin site.'))
is_active = models.BooleanField(
_('active'),
default=True,
help_text=_(
'Designates whether this user should be treated as active. '
'Unselect this instead of deleting accounts.'))
date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
language = models.CharField(_('Language'), choices=settings.LANGUAGES, default=settings.ENGLISH, max_length=2)
timezone = TimeZoneField(verbose_name=_('Timezone'), default='Europe/London')
objects = UserManager()
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email']
我使用 django-allauth 通过 Google 帐户进行注册。当现有用户(之前通过 google 电子邮件注册,而不是 Google 帐户)尝试通过 Google 帐户登录时,我们遇到错误:
<DstTzInfo 'Europe/London' LMT-1 day, 23:59:00 STD> is not JSON serializable
Traceback:
File "/webapps/myproject/tmp/venv/lib/python3.4/site-packages/django/core/handlers/base.py" in get_response
149. response = self.process_exception_by_middleware(e, request)
File "/webapps/myproject/tmp/venv/lib/python3.4/site-packages/django/core/handlers/base.py" in get_response
147. response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/webapps/myproject/tmp/venv/lib/python3.4/site-packages/allauth/socialaccount/providers/oauth2/views.py" in view
55. return self.dispatch(request, *args, **kwargs)
File "/webapps/myproject/tmp/venv/lib/python3.4/site-packages/allauth/socialaccount/providers/oauth2/views.py" in dispatch
125. return complete_social_login(request, login)
File "/webapps/myproject/tmp/venv/lib/python3.4/site-packages/allauth/socialaccount/helpers.py" in complete_social_login
142. return _complete_social_login(request, sociallogin)
File "/webapps/myproject/tmp/venv/lib/python3.4/site-packages/allauth/socialaccount/helpers.py" in _complete_social_login
158. ret = _process_signup(request, sociallogin)
File "/webapps/myproject/tmp/venv/lib/python3.4/site-packages/allauth/socialaccount/helpers.py" in _process_signup
25. request.session['socialaccount_sociallogin'] = sociallogin.serialize()
File "/webapps/myproject/tmp/venv/lib/python3.4/site-packages/allauth/socialaccount/models.py" in serialize
189. user=serialize_instance(self.user),
File "/webapps/myproject/tmp/venv/lib/python3.4/site-packages/allauth/utils.py" in serialize_instance
194. return json.loads(json.dumps(data, cls=DjangoJSONEncoder))
File "/usr/lib/python3.4/json/__init__.py" in dumps
237. **kw).encode(obj)
File "/usr/lib/python3.4/json/encoder.py" in encode
192. chunks = self.iterencode(o, _one_shot=True)
File "/usr/lib/python3.4/json/encoder.py" in iterencode
250. return _iterencode(o, 0)
File "/webapps/myproject/tmp/venv/lib/python3.4/site-packages/django/core/serializers/json.py" in default
115. return super(DjangoJSONEncoder, self).default(o)
File "/usr/lib/python3.4/json/encoder.py" in default
173. raise TypeError(repr(o) + " is not JSON serializable")
Exception Type: TypeError at /accounts/google/login/callback/
Exception Value: <DstTzInfo 'Europe/London' LMT-1 day, 23:59:00 STD> is not JSON serializable
在 allauth 中序列化自定义字段有哪些方法?
我的解决方案是替换默认的 DefaultSocialAccountAdapter 和扩展 serialize_instance(来自 allauth.utils)以序列化 TimeZoneField。不要忘记在项目设置中设置自定义 adapret:
SOCIALACCOUNT_ADAPTER = 'myapp.adapter.MySocialAccountAdapter'
我还用直接帐户(通过电子邮件注册)替换了关联社交帐户的 pre_social_login(感谢 elssar 的示例:)
myapp.adapter.py:
import json
import base64
import logging
from django.db.models import FieldDoesNotExist, FileField
from django.db.models.fields import (BinaryField)
from django.utils import six
from django.core.serializers.json import DjangoJSONEncoder
from django.shortcuts import HttpResponse
try:
from django.utils.encoding import force_text
except ImportError:
from django.utils.encoding import force_unicode as force_text
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
from allauth.account.adapter import DefaultAccountAdapter
from allauth.utils import SERIALIZED_DB_FIELD_PREFIX
from allauth.exceptions import ImmediateHttpResponse
from timezone_field import TimeZoneField
from accounts.models import User
logger = logging.getLogger("django")
def my_serialize_instance(instance):
"""Instance serializer supported of serialization of TimeZoneField.
:param instance:
:return:
"""
data = {}
for k, v in instance.__dict__.items():
if k.startswith('_') or callable(v):
continue
try:
field = instance._meta.get_field(k)
if isinstance(field, BinaryField):
v = force_text(base64.b64encode(v))
elif isinstance(field, FileField):
if not isinstance(v, six.string_types):
v = v.name
elif isinstance(field, TimeZoneField):
v = six.text_type(v.zone)
# Check if the field is serializable. If not, we'll fall back
# to serializing the DB values which should cover most use cases.
try:
json.dumps(v, cls=DjangoJSONEncoder)
except TypeError:
v = field.get_prep_value(v)
k = SERIALIZED_DB_FIELD_PREFIX + k
except FieldDoesNotExist:
pass
data[k] = v
return json.loads(json.dumps(data, cls=DjangoJSONEncoder))
class MySocialAccountAdapter(DefaultSocialAccountAdapter):
"""Custom SocialAccountAdapter for django-allauth.
Replaced standard behavior for serialization of TimeZoneField.
Need set it in project settings:
SOCIALACCOUNT_ADAPTER = 'myapp.adapter.MySocialAccountAdapter'
"""
def __init__(self, request=None):
super(MySocialAccountAdapter, self).__init__(request=request)
def pre_social_login(self, request, sociallogin):
# This isn't tested, but should work
try:
emails = [email.email for email in sociallogin.email_addresses]
user = User.objects.get(email__in=emails)
sociallogin.connect(request, user)
raise ImmediateHttpResponse(response=HttpResponse())
except User.DoesNotExist:
pass
except Exception as ex:
logger.error(ex)
def serialize_instance(self, instance):
return my_serialize_instance(instance)