新建文件common/extends/models.py,将cmdb/models.py里定义的TimeAbstract、CommonParent移到这里
from django.db import models
class TimeAbstract(models.Model):
update_time = models.DateTimeField(
auto_now=True, null=True, blank=True, verbose_name='更新时间')
created_time = models.DateTimeField(
auto_now_add=True, null=True, blank=True, verbose_name='创建时间')
class ExtMeta:
related = False
dashboard = False
class Meta:
abstract = True
ordering = ['-id']
class CommonParent(models.Model):
parent = models.ForeignKey(
"self", null=True, blank=True, on_delete=models.SET_NULL, related_name='children')
class Meta:
abstract = True
新建用户中心模块
(venv) ➜ ydevops-backend django-admin startapp ucenter
(venv) ➜ ydevops-backend mv ucenter apps
编写用户组织架构及rbac模型
from django.db import models
from django.contrib.auth.models import AbstractUser
from common.extends.models import TimeAbstract, CommonParent
# Create your models here.
def org_extra_data():
return {
'leader_user_id': '', # 存储部门领导ID
'dn': '', # 存储ldap dn
}
def user_extra_data():
return {
'ding_userid': '', # 钉钉用户ID
'feishu_userid': '', # 飞书UserID
'feishu_unionid': '', # 飞书UnionID
'feishu_openid': '', # 飞书OpenID
'leader_user_id': '', # 直属领导ID
'dn': '', # ldap dn
}
class Menu(TimeAbstract, CommonParent):
"""
菜单模型
"""
name = models.CharField(max_length=30, unique=True, verbose_name='菜单名')
title = models.CharField(max_length=30, null=True, blank=True, verbose_name='菜单显示名')
icon = models.CharField(max_length=50, null=True, blank=True, verbose_name='图标')
path = models.CharField(max_length=158, null=True, blank=True, verbose_name='路由地址')
redirect = models.CharField(max_length=200, null=True, blank=True, verbose_name='跳转地址')
is_frame = models.BooleanField(default=False, verbose_name='外部菜单')
hidden = models.BooleanField(default=False, verbose_name='是否隐藏')
spread = models.BooleanField(default=False, verbose_name='是否默认展开')
sort = models.IntegerField(default=0, verbose_name='排序标记')
component = models.CharField(max_length=200, default='Layout', verbose_name='组件')
affix = models.BooleanField(default=False, verbose_name='固定标签')
single = models.BooleanField(default=False, verbose_name='标签单开')
activeMenu = models.CharField(max_length=128, blank=True, null=True, verbose_name='激活菜单')
def __str__(self):
return self.name
class Meta:
default_permissions = ()
verbose_name = '菜单'
verbose_name_plural = verbose_name + '管理'
ordering = ['sort', 'name']
class Permission(TimeAbstract, CommonParent):
"""
权限模型
"""
name = models.CharField(max_length=30, unique=True, verbose_name='权限名')
method = models.CharField(max_length=50, null=True, blank=True, verbose_name='方法')
def __str__(self):
return self.name
class Meta:
default_permissions = ()
verbose_name = '权限'
verbose_name_plural = verbose_name + '管理'
class Role(TimeAbstract):
"""
角色模型
"""
name = models.CharField(max_length=32, unique=True, verbose_name='角色')
permissions = models.ManyToManyField(Permission, blank=True, related_name='role_permission', verbose_name='权限')
menus = models.ManyToManyField(Menu, blank=True, verbose_name='菜单')
desc = models.CharField(max_length=50, blank=True, null=True, verbose_name='描述')
def __str__(self):
return self.name
class Meta:
default_permissions = ()
verbose_name = '角色'
verbose_name_plural = verbose_name + '管理'
class Organization(TimeAbstract, CommonParent):
"""
组织架构
"""
organization_type_choices = (
('company', '公司'),
('department', '部门')
)
dept_id = models.CharField(max_length=32, unique=True, verbose_name='部门ID')
name = models.CharField(max_length=60, verbose_name='名称')
type = models.CharField(max_length=20, choices=organization_type_choices, default='department', verbose_name='类型')
extra_data = models.JSONField(default=org_extra_data, verbose_name='其它数据', help_text=f'数据格式:{org_extra_data()}')
@property
def full(self):
l = []
self.get_parents(l)
return l
def get_parents(self, parent_result: list):
if not parent_result:
parent_result.append(self)
parent_obj = self.parent
if parent_obj:
parent_result.append(parent_obj)
parent_obj.get_parents(parent_result)
def __str__(self):
return self.name
class ExtMeta:
related = True
dashboard = False
class Meta:
default_permissions = ()
verbose_name = '组织架构'
verbose_name_plural = verbose_name + '管理'
class UserProfile(TimeAbstract, AbstractUser):
"""
用户信息
"""
mobile = models.CharField(max_length=11, null=True, blank=True, verbose_name='手机号码')
avatar = models.ImageField(upload_to='static/%Y/%m', default='image/default.png',
max_length=250, null=True, blank=True)
department = models.ManyToManyField(Organization, related_name='org_user', verbose_name='部门')
# 职能:根据职能授权
position = models.CharField(max_length=50, null=True, blank=True, verbose_name='职能')
# 职位:仅展示用户title信息
title = models.CharField(max_length=50, null=True, blank=True, verbose_name='职位')
roles = models.ManyToManyField(Role, verbose_name='角色', related_name='user_role', blank=True)
extra_data = models.JSONField(default=user_extra_data, verbose_name='其它数据', help_text=f'数据格式:{user_extra_data()}')
is_ldap = models.BooleanField(default=False, verbose_name='是否ldap用户')
@property
def name(self):
if self.first_name:
return self.first_name
return self.username
def __str__(self):
return self.name
class ExtMeta:
related = True
dashboard = False
icon = 'peoples'
class Meta:
default_permissions = ()
verbose_name = '用户信息'
verbose_name_plural = verbose_name + '管理'
ordering = ['id']
安装依赖
pip install pillow
添加模块到settings.py及配置自定义的用户认证模型
INSTALLED_APPS = [
...
'ucenter.apps.UcenterConfig',
]
...
AUTH_USER_MODEL = 'ucenter.UserProfile'
调整cmdb模块里的模型
# 注释原有用户模型,替换为自定义的模型
# from django.contrib.auth.models import User
from ucenter.models import UserProfile as User
from .model_assets import Idc, Region
# 从common里导入TimeAbstract, CommonParent
from common.extends.models import TimeAbstract, CommonParent
(venv) ➜ ydevops-backend python manage.py makemigrations
Migrations for 'ucenter':
apps/ucenter/migrations/0001_initial.py
- Create model Menu
- Create model Permission
- Create model Role
- Create model Organization
- Create model UserProfile
此时创建表时会有异常
(venv) ➜ ydevops-backend python manage.py migrate
...
django.db.migrations.exceptions.InconsistentMigrationHistory: Migration admin.0001_initial is applied before its dependency ucenter.0001_initial on database 'default'.
我们现在开发阶段,最省事的就是直接把database删除,重新生成
rm db.sqlite3
python manage.py makemigrations
python manage.py migrate
当然,如果想尝试解决,我们按如下步骤
python manage.py migrate ucenter
python manage.py makemigrations
python manage.py showmigrations
由于我们使用了自定义的用户模型,需要重新创建用户
(venv) ➜ ydevops-backend python manage.py createsuperuser
class UserProfileListSerializers(serializers.ModelSerializer):
user_department = serializers.SerializerMethodField()
user_director = serializers.SerializerMethodField()
def get_user_department(self, instance):
return [{'org_id': i.id, 'org_name': i.name} for i in instance.department.all()]
def get_user_director(self, instance):
leader_ou = [i.extra_data['leader_user_id'] for i in instance.department.all() if i.extra_data.get('leader_user_id', None)]
leaders = UserProfile.objects.filter(extra_data__feishu_openid__in=leader_ou)
return [[{'id': i.id, 'name': i.name} for i in leaders]]
class Meta:
model = UserProfile
exclude = ('password', 'dn')
class UserProfileDetailSerializers(UserProfileListSerializers):
user_roles = serializers.SerializerMethodField()
routers = serializers.SerializerMethodField()
permissions = serializers.SerializerMethodField()
def get_user_roles(self, instance):
try:
qs = instance.roles.all()
return [{'id': i.id, 'name': i.name, 'desc': i.desc} for i in qs]
except BaseException as e:
return []
def get_permissions(self, instance):
perms = instance.roles.values(
'permissions__method',
).distinct()
if instance.is_superuser:
return ['admin']
return [p['permissions__method'] for p in perms if p['permissions__method']]
def get_routers(self, instance):
qs = []
if instance.is_superuser or 'admin' in [p['permissions__method'] for p in
instance.roles.values('permissions__method')]:
qs = Menu.objects.filter(parent__isnull=True)
serializer = MenuListSerializers(instance=qs, many=True)
tree_data = serializer.data
else:
[qs.extend(i.menus.all()) for i in instance.roles.all()]
serializer = UserMenuSerializers(instance=qs, many=True)
# 组织用户拥有的菜单列表
tree_dict = {}
tree_data = []
try:
for item in serializer.data:
tree_dict[item['id']] = item
for i in tree_dict:
if tree_dict[i]['parent']:
pid = tree_dict[i]['parent']
parent = tree_dict[pid]
parent.setdefault('children', []).append(tree_dict[i])
else:
tree_data.append(tree_dict[i])
except:
tree_data = serializer.data
return tree_data
class Meta:
model = UserProfile
exclude = ('avatar',)
class UserProfileSerializers(serializers.ModelSerializer):
class Meta:
model = UserProfile
exclude = ('avatar',)
def create(self, validated_data):
roles = validated_data.pop('roles')
departments = validated_data.pop('department')
instance = UserProfile.objects.create(**validated_data)
instance.set_password(validated_data['password'])
instance.save()
instance.department.set(departments)
instance.roles.set(roles)
return instance
import shortuuid
from django.db.models import Q
from django.core.cache import cache
from config import USER_AUTH_BACKEND
import logging
logger = logging.getLogger(__name__)
USER_SYNC_KEY = {
'feishu': 'celery_job:feishu_user_sync', # 同步飞书组织架构任务key
'ldap': 'celery_job:ldap_user_sync', # LDAP用户同步任务KEY
}
class UserViewSet(AutoModelViewSet):
"""
用户管理视图
### 用户管理权限
{'*': ('user_all', '用户管理')},
{'get': ('user_list', '查看用户')},
{'post': ('user_create', '创建用户')},
{'put': ('user_edit', '编辑用户')},
{'patch': ('user_edit', '编辑用户')},
{'delete': ('user_delete', '删除用户')}
"""
perms_map = (
{'*': ('admin', '管理员')},
{'*': ('user_all', '用户管理')},
{'get': ('user_list', '查看用户')},
{'post': ('user_create', '创建用户')},
{'put': ('user_edit', '编辑用户')},
{'patch': ('user_edit', '编辑用户')},
{'delete': ('user_delete', '删除用户')}
)
queryset = UserProfile.objects.exclude(Q(username='thirdparty') | Q(is_active=False))
serializer_class = UserProfileSerializers
serializer_list_class = UserProfileListSerializers
def get_serializer_class(self):
if self.action in ['detail', 'retrieve']:
return UserProfileDetailSerializers
return super().get_serializer_class()
def create(self, request, *args, **kwargs):
if self.queryset.filter(username=request.data['username']):
return ops_response({}, success=False, errorCode=40300, errorMessage='%s 账号已存在!' % request.data['username'])
password = shortuuid.ShortUUID().random(length=8)
request.data['password'] = password
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
data = serializer.data
data['password'] = password
data['status'] = 'success'
data['code'] = 20000
return ops_response(data)
def perform_destroy(self, instance):
# 禁用用户
instance.is_active = False
instance.save()
@action(methods=['POST'], url_path='password/reset', detail=False)
def password_reset(self, request):
"""
重置用户密码
### 重置用户密码
"""
data = self.request.data
user = self.queryset.get(pk=data['uid'])
if user.is_superuser:
return ops_response({}, success=False, errorCode=40300, errorMessage='禁止修改管理员密码!')
user.set_password(data['password'])
user.save()
return ops_response('密码已更新.')
@action(methods=['GET'], url_path='detail', detail=False)
def detail_info(self, request, pk=None, *args, **kwargs):
"""
用户详细列表
### 获取用户详细信息,用户管理模块
"""
return super().list(request, pk, *args, **kwargs)
@action(methods=['POST'], url_path='sync', detail=False)
def user_sync(self, request):
"""
用户同步
### 传递参数:
sync: 1
"""
sync = request.data.get('sync', 0)
is_job_exist = cache.get(USER_SYNC_KEY[USER_AUTH_BACKEND])
if is_job_exist:
return ops_response({}, success=False, errorCode=40300, errorMessage='已经有组织架构同步任务在运行中... 请稍后刷新页面查看')
if sync:
# 同步任务,后面再实现
taskid = None
# 限制只能有一个同步任务在跑
cache.set(USER_SYNC_KEY[USER_AUTH_BACKEND], taskid, timeout=300)
return ops_response('正在同步组织架构信息...')
加用户管理的路由加到devops_backend/urls.py
...
router.register('users', UserViewSet)
访问http://localhost:9000/apidoc/,输入users过滤可以看到用户模块接口
页面更新:2024-04-18
本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828
© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号