Django-rest-auth 自定义注册无法保存额外字段

8

我正在使用DRF,对于登录/注册,我正在使用Django-rest-auth。

  1. 我已经自定义了User模型以具有额外的字段
  2. 我已经自定义了注册序列化器,在注册新用户时存储额外的字段以及用户名、密码。

注册成功,但是额外的字段没有与用户名、名字、姓氏和密码一起保存。

我的模型:

class UserManager(BaseUserManager):

  def _create_user(self, username, email, password, is_staff, is_superuser, address, **extra_fields):
    now = timezone.now()
    if not username:
      raise ValueError(_('The given username must be set'))
    email = self.normalize_email(email)
    user = self.model(username=username, email=email,
             is_staff=is_staff, is_active=True,
             is_superuser=is_superuser, last_login=now,
             date_joined=now, address=address, **extra_fields)
    user.set_password(password)
    user.save(using=self._db)
    return user

  def create_user(self, username, email=None, password=None, **extra_fields):
    return self._create_user(username, email, password, False, False, True,
                 **extra_fields)

  def create_superuser(self, username, email, password, **extra_fields):
    user=self._create_user(username, email, password, True, True,
                 **extra_fields)
    user.is_active=True
    user.save(using=self._db)
    return user


class User(AbstractBaseUser, PermissionsMixin):
  username = models.CharField(_('username'), max_length=30, unique=True,
    help_text=_('Required. 30 characters or fewer. Letters, numbers and @/./+/-/_ characters'),
    validators=[
      validators.RegexValidator(re.compile('^[\w.@+-]+$'), _('Enter a valid username.'), _('invalid'))
    ])
  first_name = models.CharField(_('first name'), max_length=30, blank=True, null=True)
  last_name = models.CharField(_('last name'), max_length=30, blank=True, null=True)
  email = models.EmailField(_('email address'), max_length=255, unique=True)
  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)
  receive_newsletter = models.BooleanField(_('receive newsletter'), default=False)
  birth_date = models.DateField(_('birth date'), auto_now=False, null=True)
  address = models.CharField(_('address'), max_length=30, blank=True, null=True)
  phone_regex = RegexValidator(regex=r'^\+?1?\d{9,15}$', message="Phone number must be entered in the format: '+999999999'. Up to 15 digits allowed.")
  phone_number = models.CharField(_('phone number'), validators=[phone_regex], max_length=30, blank=True, null=True) # validators should be a list

  USER_TYPES = (
    ('Farmer', 'Farmer'),
    ('Windmill owner', 'Windmill owner'),
    ('Solar panel owner', 'Solar panel owner'),)
  user_type = models.CharField(_('user type'), choices=USER_TYPES, max_length=30, blank=True, null=True)

  USERNAME_FIELD = 'username'
  REQUIRED_FIELDS = ['email',]

  objects = UserManager()

  class Meta:
    verbose_name = _('user')
    verbose_name_plural = _('users')

  def get_full_name(self):
    full_name = '%s %s' % (self.first_name, self.last_name)
    return full_name.strip()

  def get_short_name(self):
    return self.first_name

  def email_user(self, subject, message, from_email=None):
    send_mail(subject, message, from_email, [self.email]) 

我的序列化器:
class RegisterSerializer(serializers.Serializer):
    email = serializers.EmailField(required=allauth_settings.EMAIL_REQUIRED)
    first_name = serializers.CharField(required=True, write_only=True)
    last_name = serializers.CharField(required=True, write_only=True)
    address = serializers.CharField(required=True, write_only=True)

    user_type = serializers.ChoiceField(
    choices=(('Farmer', 'Farmer'),('Windmill owner', 'Windmill owner'),('Solar panel owner', 'Solar panel owner'),),
    style={'base_template': 'radio.html'},
    required=True, write_only=True)


    password1 = serializers.CharField(required=True, write_only=True)
    password2 = serializers.CharField(required=True, write_only=True)

    def validate_email(self, email):
        email = get_adapter().clean_email(email)
        if allauth_settings.UNIQUE_EMAIL:
            if email and email_address_exists(email):
                raise serializers.ValidationError(
                    _("A user is already registered with this e-mail address."))
        return email

    def validate_password1(self, password):
        return get_adapter().clean_password(password)

    def validate(self, data):
        if data['password1'] != data['password2']:
            raise serializers.ValidationError(
                _("The two password fields didn't match."))
        return data

    def get_cleaned_data(self):
        return {
            'first_name': self.validated_data.get('first_name', ''),
            'last_name': self.validated_data.get('last_name', ''),
            'address': self.validated_data.get('address', ''),
            'user_type': self.validated_data.get('user_type', ''),
            'password1': self.validated_data.get('password1', ''),
            'email': self.validated_data.get('email', ''),
        }

    def save(self, request):
        adapter = get_adapter()
        user = adapter.new_user(request)
        self.cleaned_data = self.get_cleaned_data()
        adapter.save_user(request, user, self)
        setup_user_email(request, user, [])
        user.save()
        return user 

什么出了问题?

你正在使用哪些版本的Django、DRF和rest_auth? - vabada
Django 1.9.7,DRF 3.3.0,django_rest_auth 0.7.0(这个我从这里跟随:https://github.com/Tivix/django-rest-auth) - Thinker
我刚刚将DRF更新到3.4.0。 - Thinker
我曾经遇到过类似的问题,额外的字段没有被保存,但事实上是因为我使用了自定义注册表单而不是序列化器。我通过将 django_rest_auth 降级到版本 0.6.0 来“解决”它,但我会在几天后再试一次,因为我想将其更新到最新版本 0.7.0。如果我有什么发现,我会告诉你的。请让我知道您是否有任何消息。 - vabada
那将是非常有帮助的!顺便说一下,升级DRF版本并没有帮助!这个问题已经悬而未决,没有人回答! - Thinker
1
@dabad 答案已被接受,也许你可以看一下! - Thinker
2个回答

13

看起来django-allauth默认不允许保存自定义字段:

(参考: https://github.com/pennersr/django-allauth/blob/master/allauth/account/adapter.py#L227)

为了解决这个问题,只需在执行user.save()之前分配自定义字段的值即可。

self.cleaned_data = self.get_cleaned_data()
adapter.save_user(request, user, self)
setup_user_email(request, user, [])

user.address = self.cleaned_data.get('address')
user.user_type = self.cleaned_data.get('user_type')

user.save()
return user

那是一种不太优雅的解决方案。更好的方法是覆盖allauth适配器以支持您的自定义字段。


是的,它起作用了!非常感谢,它挂起了相当长的时间! - Thinker

4

如果要覆盖默认适配器并保存自定义字段,请尝试以下操作:

在您的应用程序根目录中创建一个adapters.py文件,并粘贴下面的代码

from allauth.account.adapter import DefaultAccountAdapter


class CustomUserAccountAdapter(DefaultAccountAdapter):

    def save_user(self, request, user, form, commit=True):
        """
        Saves a new `User` instance using information provided in the
        signup form.
        """
        from allauth.account.utils import user_field

        user = super().save_user(request, user, form, False)
        user_field(user, 'address', request.data.get('address', ''))
        user_field(user, 'first_name', request.data.get('first_name', ''))
        user_field(user, 'last_name', request.data.get('last_name', ''))
        user_field(user, 'user_type', request.data.get('user_type', ''))
        user.save()
        return user

最后,在 settings.py 文件中添加以下行,以设置配置使用您的自定义适配器:
ACCOUNT_ADAPTER = 'users.adapters.CustomUserAccountAdapter'

对于额外字段的分配,user_field 可能会失败,因为它是一个内部方法。只需使用普通的赋值方式,例如:user.user_type = 'myusertype'。 - Gonzalo
我因为这个答案而爱你。愿上帝保佑你。你节省了我很多时间。 - Opeyemi Odedeyi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接