Django allauth序列化错误自定义TimeZoneField的用户模型

时间:2016-11-24 07:25:42

标签: django serialization django-allauth google-account

我的自定义用户模型有一个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帐户登录时,我们有错误:

<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中序列化自定义字段有哪些方法?

1 个答案:

答案 0 :(得分:0)

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

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)