zoukankan      html  css  js  c++  java
  • 【18】网站搭建:自定义用户模型

    一、前言

      在Django自带的User类中,只有用户名、邮箱、密码等等一些基础信息。如果此时有添加用户电话,昵称,qq号等其他信息的需求时,自带User类的弊端就出现了。那么如果出现上述需求时,就需要自定义用户模型。

      在Django的文档中对于自定义用户模型,有下面这么两段话。

      有两种方法可以扩展默认User模型而无需替换自己的模型。如果您需要的更改纯粹是行为上的,并且不需要对数据库中存储的内容进行任何更改,则可以基于创建代理模型User。这允许代理模型提供的任何功能,包括默认排序,自定义管理器或自定义模型方法。

      如果您希望存储与之相关的信息User,可以使用 OneToOneField包含这些字段的模型来获取其他信息。这种一对一模型通常称为配置文件模型,因为它可能存储有关站点用户的非身份验证相关信息。

    二、自定义模型继承AbstractUser

    1.AbstractUser 类的部分代码

    class AbstractUser(AbstractBaseUser, PermissionsMixin):
        """
        An abstract base class implementing a fully featured User model with
        admin-compliant permissions.
    
        Username and password are required. Other fields are optional.
        """
        username_validator = UnicodeUsernameValidator() if six.PY3 else ASCIIUsernameValidator()
    
        username = models.CharField(
            _('username'),
            max_length=150,
            unique=True,
            help_text=_('Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.'),
            validators=[username_validator],
            error_messages={
                'unique': _("A user with that username already exists."),
            },
        )
        first_name = models.CharField(_('first name'), max_length=30, blank=True)
        last_name = models.CharField(_('last name'), max_length=30, blank=True)
        email = models.EmailField(_('email address'), blank=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.'
            ),
        )

    2.平时经常使用的User类

    class User(AbstractUser):
        """
        Users within the Django authentication system are represented by this
        model.
    
        Username, password and email are required. Other fields are optional.
        """
        class Meta(AbstractUser.Meta):
            swappable = 'AUTH_USER_MODEL'

      可以看出同样的方法,我也可以试着使用一个新的 User 来继承 AbstractUser 类,并添加上需要的字段。我一开始在本地开发使用的是这种办法,后来出了很多的一系列问题,比如数据库迁移失败,修改表结构,而且数据库迁移失败是由于migrations文件冲突导致的,必须删除该迁移文件,并重新生成迁移文件。考虑到这样下去,会影响到我生产环境的数据库,而且需要修改的代码也比较多,所以最后不得不弃用此方法。

      如果要具体使用这种方法,可以查看Django文档的扩展现有User模型是如何使用的。

    3.优缺点

      1) 优点:

        ①自定义强。

        ②没有不必要的字段(需要继承AbstractBaseUser)。

      2) 缺点:

        ①需要删除库来或者要项目一开始就使用。

        ②配置admin麻烦 。

    三、新的模型拓展关联User    

      除了将新模型继承 AbstractUser ,还可建立新模型使其关联User。这种方法非常适合项目已经基本成型,但在后来开发中又需要修改用户表结构的操作,最终我选用了拓展关联User的方法。

    1.官方例子

      例如创建一个Employee模型:

    from django.contrib.auth.models import User
    
    class Employee(models.Model):
        user = models.OneToOneField(User, on_delete=models.CASCADE)
        department = models.CharField(max_length=100)

      假设现有员工Fred Smith同时拥有User和Employee模型,你可以使用Django的标准相关模型约定访问相关信息:

    >>> u = User.objects.get(username='fsmith')
    >>> freds_department = u.employee.department

      要将配置文件模型的字段添加到管理员的用户页面,需要在应用程序中定义一个 InlineModelAdmin(对于此示例,我们将使用a StackedInline)admin.py并将其添加到在UserAdmin类中注册的 User类:

    from django.contrib import admin
    from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
    from django.contrib.auth.models import User
    
    from my_user_profile_app.models import Employee
    
    # Define an inline admin descriptor for Employee model
    # which acts a bit like a singleton
    class EmployeeInline(admin.StackedInline):
        model = Employee
        can_delete = False
        verbose_name_plural = 'employee'
        
    # Define a new User admin
    class UserAdmin(BaseUserAdmin):
        inlines = (EmployeeInline,)
        
    # Re-register UserAdmin
    admin.site.unregister(User)
    admin.site.register(User, UserAdmin)

      可以看到,在注册应用时,用到了admin下的StackedInline,这里与xadmin的注册有所区分,后面会提到。

    2.优缺点

      1) 优点:

        ①使用方便。

        ②不用删除库重来影响整体架构。

      2) 缺点:

        ①对比继承方法,查询速度稍稍慢一丁点。

    3.需求分析

      在一开始我就用的是Django自带的用户管理系统,所以我的用户属性只有用户名、邮箱、密码。光靠这些不是很容易记住用户的身份,应该还需要类似于昵称的属性,这样可以让用户以用户用户名进行登录,登录之后可以显示他们自己设置的昵称。

    4.新建Profile模型 

      我定义了一个Profile类,通过models的OneToOneField将其一对一地关联到User类,这里我只新增了nickname(昵称),也可以添加其他的。

    from django.db import models
    from django.contrib.auth.models import User
    
    
    class Profile(models.Model):
        user = models.OneToOneField(User, on_delete=models.CASCADE, verbose_name='用户名')
        nickname = models.CharField(max_length=20, verbose_name='昵称')
    
        def __str__(self):
            return self.nickname
    
        class Meta:
            verbose_name = '昵称'
            verbose_name_plural = '昵称'

    5.注册Profile模型     

      如果你的后台用的是admin,那么直接根据官方的例子就能正常将新的User模型注册到后台。这里我用的是xadmin后台,它与admin一样,都属于Djnago的后台管理,只不过xadmin的功能要略显强大一些,具体在Django中配置xadmin可以查看网站搭建 (第十四天) xadmin后台强化,这里就不赘述了。因为xadmin在注册应用时,继承的是object,而admin继承的是admin.ModelAdmin,所以在实现注册模型上有一定的区别。

      之前,我在xadmin模块中怎样都找不到StackedInline,心里想着完了,又要重新使用admin了。试弄了一会,发现其实InlineModelAdmin还是照样继承object,而UserAdmin在xadmin/plugins/auth 目录下。最后实现如下:

    import xadmin
    from xadmin.plugins.auth import UserAdmin as BaseAdmin
    from django.contrib.auth.models import User
    from .models import Profile
    
    
    class ProfileInline(object):
        model = Profile
        extra = 0
    
    
    class UserAdmin(BaseAdmin):
        inlines = [ProfileInline]
        list_display = ['username', 'nickname', 'email', 'is_staff', 'is_active', 'is_superuser']
    
        def nickname(self, obj):
            return obj.profile.nickname
        nickname.short_description = '昵称'
    
    
    xadmin.site.unregister(User)
    xadmin.site.register(User, UserAdmin)

      上面是将Profile模型的nickname字段注册到User中,还可以将Profile模型再注册一个应用。

    class ProfileAdmin(object):
        """
        作用:自定义文章管理工具
        admin.ModelAdmin:继承admin.ModelAdmin类
        """
        list_display = ['user', 'nickname']
    
    
    xadmin.site.register(Profile, ProfileAdmin)

      做好这一步,就可以去xadmin后台为用户添加昵称了。

    6.添加User模型的方法

      实现好模型注册以后,为了能将用户的昵称显示在页面中,还需要为User类绑定一些方法。

    def get_nickname_or_username(self):
        if Profile.objects.filter(user=self).exists():
            profile = Profile.objects.get(user=self)
            return profile.nickname
        else:
            return self.username
    
    
    def has_nickname(self):
        return Profile.objects.filter(user=self).exists()
        
    # 绑定方法不需要添加括号
    User.get_nickname_or_username = get_nickname_or_username
    User.has_nickname = has_nickname

      然后就可以在之前显示username的地方修改为nickname的显示,如导航栏的欢迎用户。

    <a href="#" class="dropdown-toggle hidden-sm" data-toggle="dropdown" role="button">
        <span class="glyphicon glyphicon-user item"></span> 您好: {{ user.get_nickname_or_username }}<span class="caret"></span>
    </a>

    7.定义修改昵称表单

      之前还是需要管理员自己到后台对用户进行添加昵称操作,要是可以让用户自己修改昵称岂不是更好。实现起来也不难,只要再建立一个form表单,将post的数据清洗一下,再将合法的昵称绑定在user上。关于Django的form表单使用,可以参考之前写的用户注册或登录表单。

      同样,在user/forms.py下新建ChangeNameForm表单。

    from django.contrib import auth
    from django.contrib.auth.models import User
    
    class ChangeNameForm(forms.Form):
        new_nickname = forms.CharField(
            label='新昵称',
            max_length=20,
            min_length=3,
            widget=forms.TextInput(
                attrs={'placeholder': '请输入新的昵称'}
            )
        )
    
        def __init__(self, *args,  **kwargs):
            if 'user' in kwargs:
                self.user = kwargs.pop('user')
            super(ChangeNameForm, self).__init__(*args, **kwargs)
    
        def clean(self):
            # 验证用户是否处在登录状态
            if self.user.is_authenticated:
                self.cleaned_data['user'] = self.user
            else:
                raise forms.ValidationError('用户尚未登录')
            return self.cleaned_data
    
        def clean_new_nickname(self):
            new_nickname = self.cleaned_data.get('new_nickname', '').strip()
            if new_nickname == '':
                raise forms.ValidationError("新的昵称不能为空")
            return new_nickname

    8.定义修改昵称的逻辑处理

    from .models import Profile
    from .forms import *
    from django.shortcuts import redirect, render
    
    def change_name(request):
        """修改昵称"""
        if request.method == 'POST':
            name_form = ChangeNameForm(request.POST, user=request.user)
            if name_form.is_valid():
                new_nickname = name_form.cleaned_data['new_nickname']
                profile, created = Profile.objects.get_or_create(user=request.user)
                profile.nickname = new_nickname
                profile.save()
                return redirect(request.GET.get('from', reverse('blog:home')))
    
        else:
            name_form = ChangeNameForm()
    
        context = {'name_form': name_form}
        return render(request, 'user/change_name.html', context)

    9.建立修改昵称视图

    {% extends 'base.html' %}
    {% load staticfiles %}
    
    {% block title %}
        修改昵称
    {% endblock %}
    
    {% block nav_login_active %}active{% endblock %}
    {% block lunbobox %}{% endblock %}
    
    {% block content %}
        <div class="container" style="margin-top: 70px;">
    
            <div class="head-login">
                <h2 class="text-info">修改昵称</h2>
                <span>与我取得联系,共同成长吧</span>
            </div>
    
            <div class="change_name">
                 <form action="" method="POST">
                 {% csrf_token %}
                 {% for field in name_form %}
                     <label for="{{ field.id_for_label }}">{{ field.label }}:</label>
                     {{ field }}
                     <p class="text-danger">{{ field.errors.as_text }}</p>
                 {% endfor %}
                    {# 错误信息标红 #}
                    <span class="text-danger">{{ login_form.non_field_errors }}</span>
                    {# <span>用户名:</span> #}
                    {# <input type="text" name="username"> #}
                    {# <span>密码:</span> #}
                    {# <input type="password" name="password"> #}
                     <button class="btn btn-primary pull-right" type="submit">修改</button>
                </form>
    
            </div>
    
        </div>
    {% endblock %}
    

      原文出处:https://jzfblog.com/detail/119,文章的更新编辑以此链接为准。欢迎关注源站文章!

  • 相关阅读:
    es6 数组的扩展
    面向对象的7大原则及其实例
    flex 布局
    一、创建vue
    箭头函数
    destructuring
    spreed&rest
    变量新声明之let、const
    jQuery之遍历索引相关方法
    jQuery之位置坐标图形相关方法
  • 原文地址:https://www.cnblogs.com/djcoder/p/10880705.html
Copyright © 2011-2022 走看看