Django快速开发可定制的办公系统实战(6):权限管理模型设计

参考资料:https://docs.djangoproject.com/en/1.11/

1 自定义用户认证模型

上一节已经将django基础数据表更新到数据库了,其中包含了用户基础权限认证,登录数据库查看user表:

mysql> DESC auth_user
    -> ;
+--------------+--------------+------+-----+---------+----------------+
| Field        | Type         | Null | Key | Default | Extra          |
+--------------+--------------+------+-----+---------+----------------+
| id           | int(11)      | NO   | PRI | NULL    | auto_increment |
| password     | varchar(128) | NO   |     | NULL    |                |
| last_login   | datetime(6)  | YES  |     | NULL    |                |
| is_superuser | tinyint(1)   | NO   |     | NULL    |                |
| username     | varchar(150) | NO   | UNI | NULL    |                |
| first_name   | varchar(30)  | NO   |     | NULL    |                |
| last_name    | varchar(30)  | NO   |     | NULL    |                |
| email        | varchar(254) | NO   |     | NULL    |                |
| is_staff     | tinyint(1)   | NO   |     | NULL    |                |
| is_active    | tinyint(1)   | NO   |     | NULL    |                |
| date_joined  | datetime(6)  | NO   |     | NULL    |                |
+--------------+--------------+------+-----+---------+----------------+
11 rows in set (0.01 sec)

Django认证系统是可以扩展的,我们可以扩展默认的User模型,或者使用自定义模型进行替换。

1.1 替换User模型:

本次项目中,替换了现有的User模型,接下了解下如何替换现有的User模型:

  1. 在项目开始时定义自己的User模型

** 这里需要强调的是在项目开始时自定义User模型,不然migrate的时候会报错,在项目实现中我会再做演示说明。**

from django.contrib.auth.models import AbstractUser

class UserProfile(AbstractUser):
    name = models.CharField(max_length=20, default="", verbose_name="姓名")
    ......
  1. 通过在settings.py里面配置AUTH_USER_MODEL指向自定义的用户模型即可替换现有的User模
    型:
AUTH_USER_MODEL = 'myapp.UserProfile'

1.2 引用自定义User模型:

需要注意的是使用自定义User模型后,如果直接引用User模型,系统是会出错的。正确的引用方法是使用 get_user_model(), 比如我们创建一个资产模型,资产通过外键引用User模型:

from django.db import models

class Asset(models.Model):

    assetNum = models.CharField(max_length=128, default="", verbose_name="资产编号")
    owner = models.ForeignKey(UserProfile, blank=True, null=True, verbose_name="责任人")

上面的做法是错误的,自定义User模型的正确使用方法如下:

from django.db import models
from django.contrib.auth import get_user_model

User = get_user_model()

class Asset(models.Model):

    assetNum = models.CharField(max_length=128, default="", verbose_name="资产编号")
    owner = models.ForeignKey(User, blank=True, null=True, verbose_name="责任人")

当然我们也可以使用 settings.AUTH_USER_MODEL来引用自定义User模型

from django.db import models
from django.conf import settings

User = settings.AUTH_USER_MODEL

class Asset(models.Model):

    assetNum = models.CharField(max_length=128, default="", verbose_name="资产编号")
    owner = models.ForeignKey(User, blank=True, null=True, verbose_name="责任人")

1.3 指定认证后端:

我们想要通过邮箱或者手机号来作为登陆认证的账户时,可以使用认证后台通过AUTHENTICATION_BACKENDS设置来指定认证后台。

  1. 编写自定义认证后端
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
from django.db.models import Q

User = get_user_model()

class UserBackend(ModelBackend):

    def authenticate(self, request, username=None, password=None, **kwargs):
        try:
            user = User.objects.get(Q(username=username) | Q(mobile=username))
            if user.check_password(password):
                return user
        except Exception as e:
            return None
  1. 指定认证后端
    在settings.py文件中加入如下内容:
AUTHENTICATION_BACKENDS = (
    'users.views_user.UserBackend',
)

更多有关Django认证系统的知识请参看文章开头的官方文档

2 权限管理模型设计

2.1 创建app

接下来我们将创建system app, system包含用户管理、菜单管理和权限管理等系统基础模块。

  • 使用pycharm打开我们的项目,右键项目根目录,选择 New → Python Package, 在弹出的窗口输入apps,这个包就用来存放项目的app.
  • 选择pycharm上方Tools,点击Run manage.py Task..., 这时在pycharm下方会打开一个窗口,输入startapp system 回车创建app, 如下图:
image
  • 将刚刚创建的system 移动到 apps下

  • 为了能够顺利访问到我们新建的app,右键apps,选择Mark Directory as → Sources root

  • 修改settings.py 加入如下内容:

import sys
sys.path.insert(0, os.path.join(BASE_DIR, 'apps'))

2.2 创建权限认证模型

权限认证模型说明:

Menu: 菜单管理,用来存储系统可用的URL
Role: 角色组,通过外键关联Menu,角色组中的用户将继承Role关联菜单的访问权限
Structure:组织架构,包含单位和部门信息
UserProfile: 自定义用户认证模型,替换系统原有的User模型

下面内容就是权限认证的模型详细内容,将如下内容复制到apps/system/models.py

from django.db import models
from django.contrib.auth.models import AbstractUser


class Menu(models.Model):
    """
    菜单
    """
    name = models.CharField(max_length=30, unique=True, verbose_name="菜单名")  # unique=True, 这个字段在表中必须有唯一值.
    parent = models.ForeignKey("self", null=True, blank=True, verbose_name="父菜单")
    icon = models.CharField(max_length=50, null=True, blank=True, verbose_name="图标")
    code = models.CharField(max_length=50, null=True, blank=True, verbose_name="编码")
    url = models.CharField(max_length=128, unique=True, null=True, blank=True)

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = '菜单'
        verbose_name_plural = verbose_name

    @classmethod
    def get_menu_by_request_url(cls, url):
        return dict(menu=Menu.objects.get(url=url))


class Role(models.Model):
    """
    角色:用于权限绑定
    """
    name = models.CharField(max_length=32, unique=True, verbose_name="角色")
    permissions = models.ManyToManyField("menu", blank=True, verbose_name="URL授权")
    desc = models.CharField(max_length=50, blank=True, null=True, verbose_name="描述")


class Structure(models.Model):
    """
    组织架构
    """
    type_choices = (("unit", "单位"), ("department", "部门"))
    name = models.CharField(max_length=60, verbose_name="名称")
    type = models.CharField(max_length=20, choices=type_choices, default="department", verbose_name="类型")
    parent = models.ForeignKey("self", null=True, blank=True, verbose_name="父类架构")

    class Meta:
        verbose_name = "组织架构"
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.name


class UserProfile(AbstractUser):
    name = models.CharField(max_length=20, default="", verbose_name="姓名")
    birthday = models.DateField(null=True, blank=True, verbose_name="出生日期")
    gender = models.CharField(max_length=10, choices=(("male", "男"), ("female", "女")),
                              default="male", verbose_name="性别")
    mobile = models.CharField(max_length=11, default="", verbose_name="手机号码")
    email = models.EmailField(max_length=50, verbose_name="邮箱")
    image = models.ImageField(upload_to="image/%Y/%m", default="image/default.jpg",
                              max_length=100, null=True, blank=True)
    department = models.ForeignKey("Structure", null=True, blank=True, verbose_name="部门")
    post = models.CharField(max_length=50, null=True, blank=True, verbose_name="职位")
    superior = models.ForeignKey("self", null=True, blank=True, verbose_name="上级主管")
    roles = models.ManyToManyField("role", verbose_name="角色", blank=True)

    class Meta:
        verbose_name = "用户信息"
        verbose_name_plural = verbose_name
        ordering = ['id']

    def __str__(self):
        return self.name

2.3 使用模型

定义好模型后,还要告诉Django使用这些模型,我们需要修改settings.py文件,在INSTALLED_APPS中添加models.py所在应用的名称:

INSTALLED_APPS = [
     ......
     'system',
    # 'apps.system.apps.SystemConfig' # 这种写法也可以
     ......
]

注意:前面已经介绍了用户自定义认证模型的使用方法,想要使用自定义的认证模型UserProfile, 还需要在setting.py中添加下面内容:

AUTH_USER_MODEL = 'system.UserProfile'

如果这里不指定AUTH_USER_MODEL的话,makemigrations时会报错

ystem.UserProfile.user_permissions: (fields.E304) Reverse accessor for 'UserProfile.user_permissions' clashes with reverse accessor for 'User.user_permissions'.
    HINT: Add or change a related_name argument to the definition for 'UserProfile.user_permissions' or 'User.user_permissions'.

最后执行makemigrations 和 migrate来生成数据表, 使用pycharm Tools,点击Run manage.py Task..., 在manage.py窗口输入下面命令:

makemigrations
migrate

你是否还记得在上面介绍使用自定义认证模型时候强调过:<font color="DarkRed"> 要在项目开始时自定义User模型 </font>

在第4节的时候埋下了一个伏笔,我执行了migrate创建生成了djang基础数据表,所以在这里使用自定义了用户认证模型,然后migrate时会报错:

django.db.migrations.exceptions.InconsistentMigrationHistory: Migration admin.0001_initial is applied before its dependency users.0001_initial on database 'default'.

这是因为在第4节中我们通过migrate在数据库中生成了django基础数据,其中包含了默认的User模型,这次自定义了User模型,再次migrate的时候,由于admin模型和默认User模型之间的依赖关系,系统会报错。处理方法:删除数据库中的所有基础数据表,重新migrate。

2.4 模型(Models)相关知识点

从这一章节开始我们会逐步介绍到当前章节创建模型使用到的知识点。

字段类型:

在权限认证模型中使用到的字段类型如下:

CharField: 用来存储字符串,必须制定一个参数 max_length用来限定字段最大长度
Foreignkey: 是一个关联字段,创建多表之间的多对一关系,如果创建同表之间的递归关联关系,可以使用models.ForeignKey('self')
ManyToManyField: 用来实现多对多的关联关系
DateField: 日期时间字段
EmailField: email字段,用来检查email地址是否合法
ImageField: 图片字段,用来定义图片上传和图片检查,需要安装pillow库

字段选项:

unique: 设置为True, 则表示这个字段必须有唯一值,这是从数据库级别来强制数据唯一,后面我们还会介绍通过form验证来确保数据输入的唯一
verbose_name:
blank: 默认值是False, 设置为True,则该字段润许为空
null: 默认值是False,如果为True,Django会在数据库中将空值转存为NULL
choices: 是一个可迭代结构(元祖),每个元组中的第一个元素,是存储在数据库中的值;第二个元素是使人容易理解的描述。

更多字段类型和字段选项请参考:

https://docs.djangoproject.com/en/1.11/ref/models/fields/#model-field-types

安装部署交流:83792608(QQ群)
更多欢迎关注:sandbox.im

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342