自定义Django用户系统

django自带用户系统,通过上面的admin,以及auth可以方便的管理用户。

不过,需求是多变的,比如,你有一个变态的用户系统,用户可能有大中小三张头像,除了fisrt name ,last name外还有middle name,T^T name巴拉巴拉,django的用户系统可能满足不了你的需求,这时候需要用自己的用户系统了,如何能在满足需求的时候充分又利用到django的用户系统?

官方文档如下,内有详细说明,有英文厌烦症的可以直接略过。
https://docs.djangoproject.com/en/dev/topics/auth/customizing/

其实步骤很简单

  1. 写自己的auth模块(定义user class);
  2. admin.py 注册到django的admin后台,并且修改一些field
  3. 修改settings.py中相应配置

 

step-1 写自己的auth模块(定义user class)

新建一个模块,名字随意,假设叫做myauth

User class继承AbstractBaseUser,UserManager继承BaseUserManager,重写对应的方法,建议浏览下AbstractBaseUser, BaseUserManager的源码,

User类不用说,也就是根据自己业务定义的用户class,Manager就是django中的Manager,做的事情你肯定经常用到,obj.objects.filter(),其中的objects就是Manager,文档如下
https://docs.djangoproject.com/en/dev/topics/db/managers/

from django.db import models
from django.contrib.auth.models import (BaseUserManager, AbstractBaseUser)


class UserManager(BaseUserManager):

  def create_user(self, name, email, password=None):

    if not email:
      raise ValueError('Users must have an email address')

    user = self.model(
      name=name,
      email=UserManager.normalize_email(email),
    )

    user.set_password(password)
    user.save(using=self._db)
    return user

  def create_superuser(self, name, email, password=None):

    user = self.create_user(name, email, password)
    user.is_admin = True
    user.save(using=self._db)
    return user


class User(AbstractBaseUser):
  '''用户表'''

  name = models.CharField(max_length=100, unique=True)
  email = models.EmailField(max_length=100, unique=True)
  avatar = models.URLField(blank=True)
  created_at = models.DateTimeField(auto_now_add=True)
  updated_at = models.DateTimeField(auto_now=True)
  is_delete = models.BooleanField(default=False)
  is_active = models.BooleanField(default=True)
  is_admin = models.BooleanField(default=False)
  access_token = models.CharField(max_length=100, blank=True)
  refresh_token = models.CharField(max_length=100, blank=True)
  expires_in = models.BigIntegerField(max_length=100, default=0)

  objects = UserManager()

  USERNAME_FIELD = 'name'
  REQUIRED_FIELDS = ('email',)

  class Meta:
    ordering = ('-created_at',)

  def __unicode__(self):
    return self.name

  def get_full_name(self):
    return self.email

  def get_short_name(self):
    return self.name

  def has_perm(self, perm, obj=None):
    return True

  def has_module_perms(self, app_label):
    return True

  @property
  def is_staff(self):
    return self.is_admin

重写的字段看下源码就可以解释到了:

# AbstractBaseUser已经有password, last_login,所以密码这些就不用费心了
# 由于get_username用到了self.USERNAME_FIELD,所以需要指明哪个字段为用户名
# get_short_name,get_full_name需要实现,否则会抛异常
# 其他就按照自己的业务来写即可
# UserManager重写下两个create方法

class AbstractBaseUser(models.Model):
  password = models.CharField(_('password'), max_length=128)
  last_login = models.DateTimeField(_('last login'), default=timezone.now)

  is_active = True

  REQUIRED_FIELDS = []

  class Meta:
    abstract = True

  def get_username(self):
    "Return the identifying username for this User"
    return getattr(self, self.USERNAME_FIELD)

  def __str__(self):
    return self.get_username()

  def natural_key(self):
    return (self.get_username(),)

  def is_anonymous(self):
    """
    Always returns False. This is a way of comparing User objects to
    anonymous users.
    """
    return False

  def is_authenticated(self):
    """
    Always return True. This is a way to tell if the user has been
    authenticated in templates.
    """
    return True

  def set_password(self, raw_password):
    self.password = make_password(raw_password)

  def check_password(self, raw_password):
    """
    Returns a boolean of whether the raw_password was correct. Handles
    hashing formats behind the scenes.
    """
    def setter(raw_password):
      self.set_password(raw_password)
      self.save(update_fields=["password"])
    return check_password(raw_password, self.password, setter)

  def set_unusable_password(self):
    # Sets a value that will never be a valid hash
    self.password = make_password(None)

  def has_usable_password(self):
    return is_password_usable(self.password)

  def get_full_name(self):
    raise NotImplementedError()

  def get_short_name(self):
    raise NotImplementedError()

step-2 admin.py 注册到django的admin后台,并且修改一些field

admin注册user,参考文档
https://docs.djangoproject.com/en/dev/ref/contrib/admin/ 
代码如下,感觉没什么需要说明的。

myauth/admin.py

from django import forms
from django.contrib import admin
from django.contrib.auth.models import Group as DjangoGroup
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.forms import ReadOnlyPasswordHashField

from myauth.models import User

# 新增用户表单
class UserCreateForm(forms.ModelForm):
  """A form for creating new users. Includes all the required
  fields, plus a repeated password."""
  password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
  password2 = forms.CharField(
    label='Password confirmation',
    widget=forms.PasswordInput,
  )

  class Meta:
    model = User
    fields = ('name', 'email')

  def clean_password2(self):
    # Check that the two password entries match
    password1 = self.cleaned_data.get("password1")
    password2 = self.cleaned_data.get("password2")
    if password1 and password2 and password1 != password2:
      raise forms.ValidationError("Passwords don't match")
    return password2

  def save(self, commit=True):
    # Save the provided password in hashed format
    user = super(UserCreateForm, self).save(commit=False)
    user.set_password(self.cleaned_data["password1"])
    if commit:
      user.save()
    return user

# 修改用户表单
class UserChangeForm(forms.ModelForm):
  """A form for updating users. Includes all the fields on
  the user, but replaces the password field with admin's
  password hash display field.
  """
  password = ReadOnlyPasswordHashField()

  class Meta:
    model = User

  def clean_password(self):
    # Regardless of what the user provides, return the initial value.
    # This is done here, rather than on the field, because the
    # field does not have access to the initial value
    return self.initial["password"]

# 注册用户
class MyUserAdmin(UserAdmin):

  form = UserChangeForm
  add_form = UserCreateForm

  list_display = ('name', 'created_at', 'email', 'is_delete', 'is_admin')
  search_fields = ('name', 'email')
  list_filter = ('is_admin',)
  readonly_fields = ('created_at', 'updated_at')
  fieldsets = (
    (None, {'fields': ('name', 'email', 'password', 'avatar',)}),
    ('Personal info', {'fields': ('created_at', 'updated_at')}),
    (
      'Open token info',
      {
        'fields': ('access_token', 'refresh_token', 'expires_in')
      }
    ),
    ('Permissions', {'fields': ('is_delete', 'is_admin', 'is_active')}),
    ('Important dates', {'fields': ('last_login',)}),
  )
  add_fieldsets = (
    (
      None,
      {
        'classes': ('wide',),
        'fields': ('name', 'email', 'password1', 'password2'),
      }
    ),
  )
  ordering = ('created_at',)
  filter_horizontal = ()


admin.site.register(User, MyUserAdmin)

step-3 修改settings.py中相应配置

添加 AUTH_USER_MODEL = 'myauth.User' 
install_app不要忘记加上myauth模块

grep django的源码可以看到,很多地方直接使用了配置AUTH_USER_MODEL

user = models.ForeignKey(settings.AUTH_USER_MODEL)

def get_user_model():
  """
  Returns the User model that is active in this project.
  """
  from django.db.models import get_model

  try:
    app_label, model_name = settings.AUTH_USER_MODEL.split('.')
  except ValueError:
    raise ImproperlyConfigured("AUTH_USER_MODEL must be of the form 'app_label.model_name'")
  user_model = get_model(app_label, model_name)
  if user_model is None:
    raise ImproperlyConfigured("AUTH_USER_MODEL refers to model '%s' that has not been installed" % settings.AUTH_USER_MODEL)
  return user_model