且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

使用django-allauth注册多个用户类型

更新时间:2023-12-02 11:14:28

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 a def 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 the first_name and last_name fields
  • the one with hidden user_type='company' and the fields from Company 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 or CompanyUserSignupForm)
  • 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!