使用django-allauth注册多个用户类型 [英] Multiple user type sign up with django-allauth
问题描述
编辑
请不要浪费时间阅读问题……这是错误的方法!
看看我自己的答案,以找到正确解决方案的分步指南(带说明)
TL; DR
如何使用django-allauth为私人和公司用户实施注册?
我正在采用的方法(是否正确?)
我有以下模型
:
class PrivateUser(models.Model):
为私人用户帐户建模
用户= models.OneToOneField(User,on_delete = models.CASCADE)
class CompanyUser(models.Model):
公司的联系人用户帐户
user = models.OneToOneField(User,on_delete = models.CASCADE)
class Company(models.Model):
建模公司属性
contact_person = m odels.OneToOneField(User,related_name ='company')
name = models.CharField(max_length = 50,null = False,blank = False)
vat_no = models.CharField(
#一些配置和验证器
)
#...其他不相关字段
现在,我必须在注册过程中区分两个用户 PrivateUser
和 CompanyUser
,而django-allauth仅官方django中指定的一种注册表单-allauth文档:
ACCOUNT_SIGNUP_FORM_CLASS(=无)
指向自定义表单类的字符串(例如
myapp.forms.SignupForm
)
,用于在注册期间要求用户提供其他输入
(例如,简报注册,生日)。此类应实现
def signup(自身,请求,用户)
方法,其中user表示新注册的
用户。
因此,为了创建唯一的表单,我创建了一个抽象模型类,其中包含 PrivateUser
和 CompanyUser
加一个(请注意 user_type
字段):
类AbstractComprehensiveUser(models.Model):
需要处理一个单一注册$ b的小的hackish模型类$ b多个用户的表单
USER_TYPE_CHOICES =(
('private','Private'),
('company','Company' ),
)
user_type = models.CharField(
max_length = 10,
blank = False,
choices = USER_TYPE_CHOICES
)
#私人和公司用户的公用字段
first_name = models.CharField(max_length = 30,blank = Fals e)
last_name = models.CharField(max_length = 30,blank = False)
#公司特定字段
company_name = models.CharField(max_length = 50,null = True, blank = True)
company_vat_no = models.CharField(
#一些配置和验证器
null = True,
blank = True
)
#其他非相关字段
class Meta:
abstract = True
注意:在此类中所有非常见字段的属性为 null = True
和 blank = True
。
然后我创建了自定义 SignupForm
,如下所示:
class SignupForm(forms.ModelForm):
first_name = form.CharField(max_length = 30)
last_name = forms.CharField(max_length = 30)
类元:
模型= AbstractComprehensiveUser
字段=(
#区分私有和c的字段公司
#用户注册
'user_type',
#私人和公司用户的公用字段
'first_name','last_name',
#公司指定字段
'company_name','company_vat_no',#等等等
)
现在的想法是使用具有两种形式的模板:
- 一个隐藏了
user_type ='private'的模板
以及first_name
和last_name
字段 - 隐藏了
user_type ='company'
的人和Company
模型中的字段
然后,在 SignupForm
中,我将收到 user_type
字段,然后可以设置适当的格式,例如:
class PrivateUserSignupForm(forms.ModelForm):
first_name =形式.CharField(最大长度= 30)
last_name =形式.CharField(最大长度= 30)
类元:
模型= PrivateUser
字段=('first_name','last_name')
问题是当我在 SignupForm.signup()
方法中检索数据时, User
模型已经写在数据库中。
我不想保存它,只是:
- 验证它
- 在
signup
方法中接收数据以填充正确的格式(PrivateUserSignupForm
或CompanyUserSignupForm
) - 验证表单
- 在没有错误的情况下保存用户和其他模型
- 在没有错误的情况下不要保存任何内容并警告用户有关错误
问题是...
- 这种方法正确吗?如果没有这种编译方法,还有其他方法可以实现此目标吗?
- 如果这种方法正确,那么我该如何处理上述工作流程?
TL; DR
我在上面写的所有杂乱的东西都是垃圾! / p>
(最终)正确解决方案
在 settings.py
删除 ACCOUNT_SIGNUP_FORM_CLASS
,我们将不使用它。
假设具有以下模型
:
class PrivateUser(models.Model):
用户=模型。 OneToOneField(User,on_delete = models.CASCADE)
class CompanyUser(models.Model):
contact_person = models.OneToOneField(User,on_delete = models.CASCADE)
company_name = models.CharField(max_length = 50,null = False,blank = False)
现在,我们想要什么是让我们的应用使用diff注册 PrivateUser
和 CompanyUser
错误的表单。
要实现这一点,我们将扩展django-allauth的 SignupForm
和 SignupView
。
在 forms.py
中:
从myapp.models导入CompanyUser
类CompanySignupForm(SignupForm):
#在此处声明CompanyUser模型中的所有其他字段没有
#用户的OneToOneField
#(注意:不要尝试使用model = CompanyUser,
#声明元类!)
company_name = form。 CharField(max_length = 50,required = True,strip = True)
#覆盖save方法以保存多余的字段
#(否则该表单将仅保存User实例)
def save(self,request):
#保存用户实例并获得对它的引用
user = super(CompanySignupForm,self).save(request)
#创建一个实例带有额外字段
#的模型,然后将其保存。
#(注意:已经清洗过了,但是如果您想做一些
#额外的清洗工作,请像往常一样覆盖清理方法)
company_user = CompanyUser(
contact_person = user ,
company_name = self.cleaned_data.get('company_name')
)
company_user.save()
#记住要返回User实例(不是您的自定义用户,
#Django用户),否则当
#complete_signup方法尝试查看它时,您将得到一个错误。
return company_user.contact_person
现在,我们有 CompanyUser
模型和 CompanySignupForm
表单。让我们在 views.py
中使用以下代码创建一个 CompanyUserSignupView
视图:
class CompanyUserSignupView(SignupView):
#可以从signup.html
#复制引用的HTML内容在django-allauth模板文件夹中
template_name ='account / signup_company.html'
#先前创建的表单类
form_class = CompanySignupForm
#仅在$ b $以下几行创建视图b#注意:使用相同的名称,否则会炸掉
view_name ='company_signup'
#我不使用它们,但是您可以覆盖它们
#(注意:以下值为默认值)
#success_url = None
#redirect_field_name ='next'
#创建视图(我们将在url模式中引用该视图)
company_signup = CompanyUserRegistrationView.as_view()
最后一步, urls .py
:
urlpatterns = [
#...
url(
r'^帐户/注册/公司/ $',
views.company_signup,
name ='signup-company'
),
]
现在,只需使用浏览器转到 http:// localhost:8000 / accounts / signup / company (或根据您的配置使用适当的网址格式)。
您将找到额外的字段,并可以注册公司用户。
现在重复上述所有步骤,以创建 PrivateSignupForm
表单, PrivateUserSignupView
视图并添加正确的网址格式以允许用户以私人身份注册。
最后警告
除非您将
替换为您的其中一个url,否则django-allauth默认注册URL仍然可以使用。
EDIT
Please, do not waste your time reading the question... it is the wrong approach!
Look at my own answer for a step-by-step guide (with explanation) of the right solution
TL;DR
How could I implement sign up for private and company users using django-allauth?
The approach I'm following (is it correct?)
I have the following models
:
class PrivateUser(models.Model):
"""Models a private user account"""
user = models.OneToOneField(User, on_delete=models.CASCADE)
class CompanyUser(models.Model):
"""Models the company's contact person user account"""
user = models.OneToOneField(User, on_delete=models.CASCADE)
class Company(models.Model):
"""Models the company attributes"""
contact_person = models.OneToOneField(User, related_name='company')
name = models.CharField(max_length=50, null=False, blank=False)
vat_no = models.CharField(
# some config and validators
)
# ... other non-relevant fields
Now, I have to distinguish between the two users PrivateUser
and CompanyUser
during the sign up process with django-allauth having just one sign up form as specified in the official django-allauth documentation:
ACCOUNT_SIGNUP_FORM_CLASS (=None)
A string pointing to a custom form class (e.g.
myapp.forms.SignupForm
) that is used during signup to ask the user for additional input (e.g. newsletter signup, birth date). This class should implement adef signup(self, request, user)
method, where user represents the newly signed up user.
So, to create a unique form I created an abstract model class with all the
fields from the PrivateUser
and the CompanyUser
plus one (note the user_type
field):
class AbstractComprehensiveUser(models.Model):
"""
Little hackish model class needed to handle one single sign up
form for multiple users
"""
USER_TYPE_CHOICES = (
('private', 'Private'),
('company', 'Company'),
)
user_type = models.CharField(
max_length=10,
blank=False,
choices=USER_TYPE_CHOICES
)
# Common fields for either private and company users
first_name = models.CharField(max_length=30, blank=False)
last_name = models.CharField(max_length=30, blank=False)
# Company specific fields
company_name = models.CharField(max_length=50, null=True, blank=True)
company_vat_no = models.CharField(
# some config and validators
null=True,
blank = True
)
# other non-relevant fields
class Meta:
abstract = True
N.B: all the non-common fields have in this class the attributes null=True
and blank=True
.
Then I created my custom SignupForm
as follow:
class SignupForm(forms.ModelForm):
first_name = forms.CharField(max_length=30)
last_name = forms.CharField(max_length=30)
class Meta:
model = AbstractComprehensiveUser
fields = (
# Field to differentiate from private and company
# user sign up
'user_type',
# Common fields for either private and company users
'first_name', 'last_name',
# Company specifc fields
'company_name', 'company_vat_no', # etc etc
)
The idea, now, is to use a template with two forms:
- the one with hidden
user_type='private'
and just thefirst_name
andlast_name
fields - the one with hidden
user_type='company'
and the fields fromCompany
model
Then, in the SignupForm
I will receive the user_type
field and I could set the proper form, for example:
class PrivateUserSignupForm(forms.ModelForm):
first_name = forms.CharField(max_length=30)
last_name = forms.CharField(max_length=30)
class Meta:
model = PrivateUser
fields = ('first_name', 'last_name')
The problem is that when I retrieve data in the SignupForm.signup()
method, the User
model is already written in the database.
I would like to do not save it, but just:
- validating it
- receive data in the
signup
method to populate the correct form (PrivateUserSignupForm
orCompanyUserSignupForm
) - validate the form
- in case of no errors save the user and the other models
- in case of error do not save nothing and warn the user about the error(s)
The question are...
- is this approach correct? There's some other way to accomplish this without these compilcation?
- if this approach is correct, how could I handle the workflow described just above?
TL;DR
All the messy stuff I wrote above are junk!
The (final) right solution
In settings.py
remove ACCOUNT_SIGNUP_FORM_CLASS
, we won't use it.
Suppose to have the following models
:
class PrivateUser(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
class CompanyUser(models.Model):
contact_person = models.OneToOneField(User, on_delete=models.CASCADE)
company_name = models.CharField(max_length=50, null=False, blank=False)
Now, what we want is to let our app signup the PrivateUser
and the CompanyUser
with different forms.
To accomplish that we'll extends the django-allauth's SignupForm
and SignupView
.
In forms.py
:
from myapp.models import CompanyUser
class CompanySignupForm(SignupForm):
# declare here all the extra fields in CompanyUser model WITHOUT
# the OneToOneField to User
# (N.B: do NOT try to declare Meta class with model=CompanyUser,
# it won't work!)
company_name = forms.CharField(max_length=50, required=True, strip=True)
# Override the save method to save the extra fields
# (otherwise the form will save the User instance only)
def save(self, request):
# Save the User instance and get a reference to it
user = super(CompanySignupForm, self).save(request)
# Create an instance of your model with the extra fields
# then save it.
# (N.B: the are already cleaned, but if you want to do some
# extra cleaning just override the clean method as usual)
company_user = CompanyUser(
contact_person=user,
company_name=self.cleaned_data.get('company_name')
)
company_user.save()
# Remember to return the User instance (not your custom user,
# the Django one), otherwise you will get an error when the
# complete_signup method will try to look at it.
return company_user.contact_person
Now, we have CompanyUser
model and CompanySignupForm
form. Let's create a CompanyUserSignupView
view in views.py
with the following code:
class CompanyUserSignupView(SignupView):
# The referenced HTML content can be copied from the signup.html
# in the django-allauth template folder
template_name = 'account/signup_company.html'
# the previously created form class
form_class = CompanySignupForm
# the view is created just a few lines below
# N.B: use the same name or it will blow up
view_name = 'company_signup'
# I don't use them, but you could override them
# (N.B: the following values are the default)
# success_url = None
# redirect_field_name = 'next'
# Create the view (we will reference to it in the url patterns)
company_signup = CompanyUserRegistrationView.as_view()
Last step, the urls.py
:
urlpatterns = [
# ...
url(
r'^accounts/signup/company/$',
views.company_signup,
name='signup-company'
),
]
Now, just use your browser to go to http://localhost:8000/accounts/signup/company (or the proper url pattern based on your configuration).
You will find the extra fields and you can signup a company user.
Now repeat all the previous steps to create a PrivateSignupForm
form, a PrivateUserSignupView
view and add the proper url pattern to let users signup as privates.
LAST WARNING
The django-allauth default signup url will still works unless you override it with one of your url... and you should do that!
这篇关于使用django-allauth注册多个用户类型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!