2.3 用户模块
开发用户模块的主要目标是根据用户的特定需求定制和调整系统。系统需要在正确的时间对用户的请求做出正确的响应。要达到这个目的,需要建立正确的用户模型。对于绝大多数Web应用来说,用户模块是至关重要的。
2.3.1 Django自带的用户模块
Django自带一个用户模型。要使用这个模块,需要确保在项目的settings.py文件中有下面的配置:
Django实现用户模型的代码路径在django/contrib/auth/models.py文件中,摘录如下:
在上面的模型中,有一些关键字段的含义需要加以说明:
- username:该字段表示用户名。在Django中,这个字段用来标识一个用户,不同用户的username是不一样的。
- first_name:对于东方人来说,该字段表示用户的姓氏;对于西方人来说,该字段表示用户的名字。
- last_name:和first_name相对应。对于东方人来说,该字段表示用户的名字;对于西方人来说,该字段表示用户的姓氏。
- email:该字段表示用户的电子邮箱,可以为空字符串。
- is_staff:该字段表示用户是否是内部员工。该字段为1时,表示用户是员工;该字段为0时,表示用户不是员工。Django框架最初是为报纸网站开发的,该网站同时也为非内部员工和内部员工服务,因此设置了这个字段。
- is_active:该字段表示用户是否处于活跃状态。1表示用户处于活跃状态;0表示用户处于不活跃状态。
- date_joined:该字段表示创建用户的日期。
Django的模型可映射成数据库的模式,以MySQL为后端数据库为例,最后的建表SQL如下(由于AbstractUser类还继承了另外两个类,建表语句会多出password、last_login、is_superuser这3个字段):
除了自带模块,Django还自带了权限管理系统、分组管理系统。我们将在后面的章节讨论这个话题。
可以看到,Django自带的用户系统记录了用户的用户名、密码、姓名、电子邮箱、加入的时间、是否活跃、是否是员工等信息,在很多情况下,这些信息对于一个网站来说是足够的。不过对于老赵这样卖女式高跟鞋的商人来说,知道用户的性别是很重要的;不同年龄段的女性可能对不同款式的鞋有不一样的喜好,因此最好能知道用户的年龄。
在这样的需求下,有必要对默认的用户模型做一些修改,下面我们来看看该怎么做。
2.3.2 一对一扩展用户模型
当需要在Django自带模型外另外存储一些信息的时候,可以使用一个关联表,有时也称为用户档案(User Profile)。这是一种非常常见的做法。
需要注意的是,采用这个方法后,会在获取用户信息时查询两个表,这是额外的开销。示例代码如下:
简单说明一下新建的用户档案模型的字段。
- user:该字段和Django自带用户模型是一对一的关系,表示Django自带用户模型中的用户。
- gender:该字段表示性别,M表示男性;F表示女性。
- birth_date:该字段用于记录用户的出生日期,可据此计算用户的年龄。
用户档案总是随着用户的创建而创建的。在编写代码时,创建用户的代码可能会在多个地方出现,为了避免出现重复创建用户档案的代码,现在定义一个信号,在创建用户的时候自动创建档案。代码如下:
在上面的代码中,首先从django.db.models.signals中引入post_save信号,这个信号在模型调用save方法后发出。在创建用户档案时,调用receiver对其进行装饰,这样在User模型调用save方法后,会调用注册的方法,从而达到自动创建和保存档案的目的。
定义好模型后,接下来在视图和模板中展示模型的信息。在模板中,我们可以选择展示用户名、性别和出生日期,在视图函数传入用户的上下文后。模板示例如下:
用户更新个人信息是一个常见操作,在业务逻辑中,我们可以获取一个用户对象,然后直接调用profile对档案进行修改,最后调用save方法对数据进行更新。示例代码如下:
在更新用户档案的时候,浏览器上传一个表单,然后由服务器处理这个表单,在Django中定义这样的表单是非常容易的,因为表单的字段和模型的字段非常接近,我们可以使用forms.ModelForm类创建表单。示例代码如下:
创建表单后,需要修改对应的视图函数,对于这个视图函数来说有两个任务:一个是处理使用POST方法上传的数据;另一个是处理使用GET方法的请求返回表单模板。示例代码如下:
在上面的代码中,首先判断HTTP的请求方法。如果请求的是POST方法,则调用UserForm的is_valid方法来对表单数据进行验证。验证通过后调用表单的save方法(因为表单关联了模型,所以也会调用模型的save方法),将用户档案保存到数据库,完成操作后,将网页重定向到用户档案的展示页面;验证失败则返回错误页面。
如果请求的是GET方法,则放回表单用于展示。
在我们的场景下,向不同用户展示的业务大致是一样的,只是包含的用户信息略有不同,这里可以使用Django自带的模板。示例代码如下:
上面的模板非常简单,它展示了一个用户表单、一个用户档案表单和一个提交按钮。
2.3.3 继承AbstractBaseUser
这个方法相对麻烦一些。假设现在我们的用户系统完全不需要username这个字段;我们也不用Django自带的控制台,因此is_staff字段也用不着;我们需要用email来标记一个用户。
我们可以采用继承AbstractBaseUser的方法来完成这个需求。代码如下:
上面的代码尽可能地贴近了现有的用户模型。值得注意的是,要继承AbstractBaseUser,有一些限制条件,具体如下。
- 定义USERNAME_FIELD:这个字段用作用户的唯一标识符,在定义的字段中需要设置unique=True。
- 定义REQUIRED_FIELDS:在使用createsuperuser命令创建用户时将提示的字段名称列表。
- 定义is_active字段。
- 定义get_full_name( )方法。
- 定义get_short_name( )方法。
现在要自定义一个UserManager,在框架自带的Manager中实现create_user方法和create_superuser方法,由于字段有了一些变化,需要重新实现这两个方法。代码如下:
怎么在代码中使用我们自定义的模型呢?首先要修改settings.py中的AUTH_USER_MODEL字段。代码如下:
AUTH_USER_MODEL = 'shoes.User' # shoes是应用名
在代码中可以直接通过引入自定义User的代码路径来使用新的用户模型,不过考虑到重用,应该使用下面的方式:
2.3.4 继承AbstractUser
要在框架自带用户模型中添加若干个字段,可以采用继承AbstractUser方法。这个方法比继承AbstractBaseUser要简单一些,直接加上用户的性别和用户的出生日期即可。示例代码如下:
接下来修改settings.py中的AUTH_USER_MODEL配置。代码如下:
AUTH_USER_MODEL = 'shoes.User' # shoes是应用名
值得注意的是,使用继承AbstractUser和AbstractBaseUser这两种方法来扩展用户模型时要特别小心,因为这样做会改变数据库的表结构。在本书中,我们使用Profile类来保存用户信息。