【问题标题】:Django allauth Serialization error custom User model with TimeZoneField带有 TimeZoneField 的 Django allauth 序列化错误自定义用户模型
【发布时间】:2017-04-08 09:28:44
【问题描述】:

我的自定义用户模型有一个 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 中序列化自定义字段的方法有哪些?

【问题讨论】:

    标签: django serialization django-allauth google-account


    【解决方案1】:

    我的解决方案是替换默认的 DefaultSocialAccountAdapter 和扩展 serialize_instance(来自 allauth.utils)来序列化 TimeZoneField。不要忘记在项目设置中设置自定义 adapret:

    SOCIALACCOUNT_ADAPTER = 'myapp.adapter.MySocialAccountAdapter'
    

    我还用直接帐户(通过电子邮件注册)替换了关联社交帐户的 pre_social_login(感谢 elssar 他的示例:https://stackoverflow.com/a/19443127/4012716

    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)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-03-13
      • 2013-09-22
      • 1970-01-01
      • 2023-03-26
      • 2016-08-08
      • 2013-07-12
      • 2021-01-25
      • 2016-01-08
      相关资源
      最近更新 更多