使用django-allauth保存自定义用户模型

8

我有一个 Django 自定义用户模型 MyUser,其中包含一个额外的字段:

# models.py
from django.contrib.auth.models import AbstractUser

class MyUser(AbstractUser):
    age = models.PositiveIntegerField(_("age"))

# settings.py
AUTH_USER_MODEL = "web.MyUser"

根据这些说明,我还自定义了所有认证的注册表单类

# forms.py
class SignupForm(forms.Form):
    first_name = forms.CharField(max_length=30)
    last_name = forms.CharField(max_length=30)
    age = forms.IntegerField(max_value=100)

    class Meta:
        model = MyUser

    def save(self, user):
        user.first_name = self.cleaned_data['first_name']
        user.last_name = self.cleaned_data['last_name']
        user.age = self.cleaned_data['age']
        user.save()

# settings.py
ACCOUNT_SIGNUP_FORM_CLASS = 'web.forms.SignupForm'

提交SignupForm(为属性MyUser.age渲染正确)后,我收到以下错误提示:

IntegrityError at /accounts/signup/
(1048, "Column 'age' cannot be null")

请问存储自定义用户模型的正确方法是什么? django-allauth: 0.12.0; django: 1.5.1; Python 2.7.2

我的猜测是 user.age = self.cleaned_data['age'] 在某种程度上被评估为 None。你能验证一下这个值吗?还有,user 参数被发送到 save 方法中是什么? - karthikr
你必须在save方法中返回user,也要写上return user - Aamir Rind
@karthikr 我发现save()方法没有被执行。当在django-allauth/allauth/account/forms.py#269中创建新用户时,会抛出异常。 - illagrenan
IntegrityError(完整性错误):“列'age'不能为空” - illagrenan
@illagrenan 我建议重写表单的 clean 方法而不是 save 方法。这样,在保存之前就可以捕获错误。我仍然觉得 def save(self, user) 不正确。 - karthikr
@karthikr 不起作用,我的“clean”方法没有被执行。 Django all-auth内部类“SignupForm”正在继承我的自定义 SignupForm请参见Github上的代码)。 方法save(self, user)来自all-auth文档:“(...)此类应实现一个'保存'方法,接受新注册的用户作为其唯一参数。” - illagrenan
3个回答

14

虽然有点晚了,但以防对某些人有所帮助。

你需要通过子类化DefaultAccountAdapter并设置自定义的AccountAdapter来创建自己的Custom AccountAdapter。

class UserAccountAdapter(DefaultAccountAdapter):

    def save_user(self, request, user, form, commit=True):
        """
        This is called when saving user via allauth registration.
        We override this to set additional data on user object.
        """
        # Do not persist the user yet so we pass commit=False
        # (last argument)
        user = super(UserAccountAdapter, self).save_user(request, user, form, commit=False)
        user.age = form.cleaned_data.get('age')
        user.save()

同时,您还需要在设置中定义以下内容:

ACCOUNT_ADAPTER = 'api.adapter.UserAccountAdapter'

如果您有一个自定义的注册表单来创建其他模型并且需要进行原子事务(除非它们全部成功,否则防止任何数据保存到数据库),那么这也非常有用。

对于 django-allauth 的 DefaultAdapter,它会保存用户,因此,如果您的自定义注册表单的 save 方法中出现错误,则用户仍将持久保存到数据库中。

因此,对于遇到此问题的任何人,您的 CustomAdapter 将如下所示:

class UserAccountAdapter(DefaultAccountAdapter):

    def save_user(self, request, user, form, commit=False):
        """
        This is called when saving user via allauth registration.
        We override this to set additional data on user object.
        """
        # Do not persist the user yet so we pass commit=False
        # (last argument)
        user = super(UserAccountAdapter, self).save_user(request, user, form, commit=commit)
        user.age = form.cleaned_data.get('age')
        # user.save() This would be called later in your custom SignupForm

那么您可以使用@transaction.atomic来装饰自定义的SignupForm。

@transaction.atomic
def save(self, request, user):
    user.save() #save the user object first so you can use it for relationships
    ...

编辑后添加一个例子,说明何时使用CustomAdapter会很有用 :) - danidee
谢谢您的回答,但是有没有办法只更新allauth中的社交账户模型而不是用户模型?save_user会更新用户模型吗? - rammanoj
我们应该把这个适配器放在哪个文件中? - Brendan Metcalfe

1

侧记

使用Django 1.5自定义用户模型的最佳实践是使用get_user_model函数:

from django.contrib.auth import get_user_model

# forms.py
class SignupForm(forms.Form):
    first_name = forms.CharField(max_length=30)
    last_name = forms.CharField(max_length=30)
    age = forms.IntegerField(max_value=100)

    class Meta:
        model = get_user_model() # use this function for swapping user model

    def save(self, user):
        user.first_name = self.cleaned_data['first_name']
        user.last_name = self.cleaned_data['last_name']
        user.age = self.cleaned_data['age']
        user.save()

# settings.py
ACCOUNT_SIGNUP_FORM_CLASS = 'web.forms.SignupForm'

也许这与此无关,但我认为值得注意。

我想让属性age成为必填项,但是django-allauth仅尝试使用默认字段保存MyUser。感谢get_user_model函数,我之前不知道它的存在。 - illagrenan

0

我认为你应该在SignupForm的类Meta中定义fields属性,并设置包含age的字段列表,如下:

class SignupForm(forms.Form):
...
   class Meta:
      model = MyUser
      fields = ['first_name', 'last_name', 'age']

如果它没有起作用,请查看this


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