Django多个用户、后端和通用视图

-2 投票
1 回答
569 浏览
提问于 2025-04-18 09:56

我正在学习Django的认证系统,有几个问题想请教。

我想创建多个用户,并使用邮箱进行认证。所以,我用了AbtractBaseUserAbtractBaseManager。我查阅了Django的文档,还看了这个网站上的问题:用Django 1.5实现多种用户类型

我想用这个方法。因此,我尝试实现第二点:我的两个用户类型的所有字段都放在一个模型里,通过user_type变量来选择显示哪些字段。我的代码如下:

models.py

# -*- coding: utf-8 -*-

from django.db import models
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import (
    BaseUserManager, AbstractBaseUser
)

TYPE_USER = (
    ('student', _('Student')),
    ('employee', _('Employee')),
)

class MyuserManager(BaseUserManager):
    def create_user(self, email, last_name, first_name, date_of_birth, user_type, password=None):
        if not email:
            raise ValueError("users must have an email address")

        user = self.model(
            email         = self.normalize_email(email),
            last_name     = last_name,
            first_name    = first_name,
            date_of_birth = date_of_birth,
            user_type     = user_type,
        )

        user.set_password(password)
        user.save()
        return user

    def create_superuser(self, email, password, last_name, first_name, date_of_birth, user_type):
        user = self.create_user(email,
            password      = password,
            date_of_birth = date_of_birth,
            last_name     = last_name,
            first_name    = first_name,
            user_type     = user_type,
        )
        user.is_admin = True
        user.is_staff = True
        user.save()
        return user

class MyUser(AbstractBaseUser):
    """Based model user"""
    email   = models.EmailField(
        verbose_name = 'email',
        max_length=255,
        unique=True,
    )
    last_name     = models.CharField(max_length=30)
    first_name    = models.CharField(max_length=30)
    date_of_birth = models.DateField()
    user_type     = models.CharField(_('Type'), choices=TYPE_USER, max_length=20, default='student')
    phone         = models.CharField(max_length=20, blank=True)
    friends       = models.ManyToManyField("self", blank=True)
    faculty       = models.ForeignKey(Faculty, null=True, blank=True)
    desk          = models.CharField(max_length=30, blank=True)
    campus        = models.ForeignKey(Campus, null=True, blank=True)
    job           = models.ForeignKey(Function, null=True, blank=True)
    cursus        = models.ForeignKey(Cursus, null=True, blank=True)
    year          = models.IntegerField(null=True, blank=True)
    is_active     = models.BooleanField(default=True)
    is_admin      = models.BooleanField(default=False)
    is_staff      = models.BooleanField(default=False)

    objects = MyuserManager()
    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['last_name', 'first_name', 'date_of_birth', 'user_type']

问题:你觉得这样做正确吗?还是应该创建一个包含共同字段的类和两个不同用户类型的类?

接下来,我想使用自己的后端,所以我在项目中实现了一个backend.py文件。

backend.py

from fbLike.models import MyUser
from django.contrib.auth.backends import ModelBackend


class EmailAuthBackend(ModelBackend):
    def authenticate(self, email=None, password=None, **kwargs):
        try:
            user = MyUser.objects.get(email=email)
            if user.check_password(password):
                return user
        except MyUser.DoesNotExist:
            return None

    def get_user(self, user_id):
        try:
            return MyUser.objects.get(pk=user_id)
        except:
            return None

对我来说,这似乎是一个正确的实现。

问题:但是,如何进行多表继承?这可能吗?如果我有例如:

class BaseUser(AbstractBaseUser):
  email = models.EmailField(max_length=254, unique=True)
  # common fields


class Student(BaseUser):
  first_name = models.CharField(max_length=30)
  last_name = models.CharField(max_length=30)
  # ...


class employee(BaseUser):
  company_name = models.CharField(max_length=100)
  # ...

最后,结合我的models.py和backend.py,我想在views.py中使用CRUD系统来创建和更新用户。

以下是我的文件:

forms.py

class StudentProfileForm(forms.ModelForm):
    phone = forms.CharField(required=True)
    faculty = forms.ModelChoiceField(Faculty.objects.all())
    campus = forms.ModelChoiceField(Campus.objects.all())
    cursus = forms.ModelChoiceField(Cursus.objects.all())
    year = forms.IntegerField(required=True)

    class Meta:
        model = MyUser
        fields = ('email', 'password', 'first_name', 'last_name', 
                  'date_of_birth', 'phone', 'faculty', 'campus', 'cursus', 'year'
        )
        widgets = {
            'password': forms.PasswordInput(render_value=True),
        }

class EmployeeProfileForm(forms.ModelForm):
    date_of_birth = forms.DateField(widget = AdminDateWidget)
    phone = forms.CharField(required=True)
    faculty = forms.ModelChoiceField(Faculty.objects.all())
    campus = forms.ModelChoiceField(Campus.objects.all())
    job = forms.ModelChoiceField(Function.objects.all())
    desk = forms.IntegerField(required=True)

    class Meta:
        model = MyUser
        fields = ('email', 'password', 'first_name', 'last_name', 
                  'date_of_birth', 'phone', 'faculty', 'campus', 'job', 'desk'
        )
        widgets = {
            'password': forms.PasswordInput(render_value=True),
        }

在这里,我觉得这太复杂了,但我不知道怎么简化。

views.py

class RegisterProfile(CreateView):
    model = MyUser
    template_name = 'fbLike/user_profile.html'
    form_class = StudentProfileForm
    second_form_class = EmployeeProfileForm

    def get_object(self):
        return super(RegisterProfile, self).get_object()

    def get_success_url(self):
        return reverse('login',)

    def get_context_data(self, **kwargs):
        context = super(RegisterProfile, self).get_context_data(**kwargs)
        if 'studenForm' not in context:
            context['studentForm'] = self.form_class
        if 'employeeForm' not in context:
            context['employeeForm'] = self.second_form_class
        return context


    def form_valid(self, form):
        self.object = MyUser.objects.create_user(**form)
        return super(RegisterProfile, self).form_valid(form)

    def form_invalid(self, form):
        return super(RegisterProfile, self).form_invalid(form)

    def post(self, request, *args, **kwargs):
        self.object = None
        if request.POST['profileType'] == 'student':
            form = self.form_class(request.POST, prefix='st')
            form.user_type = 'student'
        else:
            form = self.second_form_class(request.POST, prefix='em')
            form.user_type = 'employee'

        if form.is_valid():
            return self.form_valid(form)
        else:
            return self.form_invalid(form)


class UpdateProfile(UpdateView):
    model = MyUser
    template_name = 'fbLike/modify_profile.html'

    def get_form_class(self):
        if self.request.user.user_type == 'student':
            return StudentProfileForm
        return EmployeeProfileForm

    def get_success_url(self):
        return reverse('welcome',)

对于CreateProfile,我想在模板中发送两个表单,借助JavaScript,我可以选择正确的表单。但是当我提交表单时,用户密码出现了错误。当我在管理页面检查用户时,哈希密码系统似乎失败了。

所以,我的第一个解决尝试是覆盖表单中的save方法,使用request.user.set_password,但这并没有奏效。

我知道这解释得很长。但如果有人能给我一个包含两个表单的CreateView类的例子就太好了。如果不能用类而只能用函数,那这个函数该怎么实现呢?

提前感谢大家!

1 个回答

2

我会尝试回答你多个问题,但请以后每个帖子只问一个问题。我知道你是新手,但StackOverflow不是讨论论坛,建议你看看帮助部分(每个页面底部都有链接),特别是关于提问的部分


我想创建多个用户,并使用电子邮件进行认证。[...] 你觉得这样做对吗?还是应该创建一个包含共同字段的类和两个不同类型用户的类?

如果这样做对你有效,那就是正确的。这类问题更适合在codereview.stackexchange.com上讨论。特别是因为这里并没有bug。

但是,如何进行多表继承?这可能吗?如果我有[...],在检查密码之前,如何知道用户的类型?

首先,认证后端是一个完全不同的话题;它和多表继承没有关系(我想你是指关系或多用户档案)。

如果每种用户类型的密码认证算法不同,你才需要多个认证后端。如果所有用户的密码都存储在同一个地方/系统/数据库中,那就不需要多个认证后端。

你应该在基础用户类中实现一个方法,返回用户的类型。然后你可以使用user_passes_check装饰器来限制你视图中的访问。

这样你就能实现你想要的效果,比如“这个部分只对学生开放”等等。

最后,在我的models.py和backend.py中,我会尝试在views.py中使用CRUD系统来创建和更新用户[...] 我觉得这太复杂了,但我不知道怎么减少复杂性。

你不需要在ModelForm中重复模型的所有字段,只有在需要修改字段或添加额外的“非模型”字段时,才需要重写字段。你可以从简单的ModelForm开始,像这样:

class UserForm(forms.ModelForm):
    class Meta:
        model = User
        fields = ('name', 'email', 'password')

添加额外字段的常见用法是要求确认密码,可以再加一个密码字段。

对于CreateProfile,我想在模板中发送两个表单,借助JavaScript,我可以选择正确的表单。但是当我提交表单时,用户密码出现错误。当我在管理页面检查用户时,哈希密码系统似乎失败了。所以,我的第一个解决尝试是重写表单中的保存方法,使用request.user.set_password,但这并不奏效。

使用表单向导来实现多步骤注册过程。在第一步中,询问基本信息和要创建的账户类型;在第二步中,只发送相关的表单,而不是发送两个表单并用JavaScript进行切换。

我在你的代码中没有看到你使用set_password的地方,所以无法对第二部分做出评论。

撰写回答