Merge pull request #7825 from jumpserver/dev

v2.20.0-rc2
pull/7919/head
Jiangjie.Bai 2022-03-14 10:38:35 +08:00 committed by GitHub
commit a64ec8a1d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 118 additions and 67 deletions

View File

@ -214,7 +214,7 @@ class DatesLoginMetricMixin:
class IndexApi(DatesLoginMetricMixin, APIView): class IndexApi(DatesLoginMetricMixin, APIView):
http_method_names = ['get'] http_method_names = ['get']
rbac_perms = { rbac_perms = {
'GET': 'rbac.view_dashboard' 'GET': 'rbac.view_audit | rbac.view_console'
} }
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):

View File

@ -390,7 +390,6 @@ class Config(dict):
'HELP_DOCUMENT_URL': 'http://docs.jumpserver.org', 'HELP_DOCUMENT_URL': 'http://docs.jumpserver.org',
'HELP_SUPPORT_URL': 'http://www.jumpserver.org/support/', 'HELP_SUPPORT_URL': 'http://www.jumpserver.org/support/',
'TICKETS_ENABLED': True,
'FORGOT_PASSWORD_URL': '', 'FORGOT_PASSWORD_URL': '',
'HEALTH_CHECK_TOKEN': '', 'HEALTH_CHECK_TOKEN': '',
} }

View File

@ -119,7 +119,6 @@ CHANGE_AUTH_PLAN_SECURE_MODE_ENABLED = CONFIG.CHANGE_AUTH_PLAN_SECURE_MODE_ENABL
DATETIME_DISPLAY_FORMAT = '%Y-%m-%d %H:%M:%S' DATETIME_DISPLAY_FORMAT = '%Y-%m-%d %H:%M:%S'
TICKETS_ENABLED = CONFIG.TICKETS_ENABLED
REFERER_CHECK_ENABLED = CONFIG.REFERER_CHECK_ENABLED REFERER_CHECK_ENABLED = CONFIG.REFERER_CHECK_ENABLED
CONNECTION_TOKEN_ENABLED = CONFIG.CONNECTION_TOKEN_ENABLED CONNECTION_TOKEN_ENABLED = CONFIG.CONNECTION_TOKEN_ENABLED

View File

@ -14,6 +14,7 @@ from orgs.models import Organization
from orgs.hands import set_current_org, Node, get_current_org from orgs.hands import set_current_org, Node, get_current_org
from perms.models import (AssetPermission, ApplicationPermission) from perms.models import (AssetPermission, ApplicationPermission)
from users.models import UserGroup, User from users.models import UserGroup, User
from assets.models import SystemUser
from common.const.signals import PRE_REMOVE, POST_REMOVE from common.const.signals import PRE_REMOVE, POST_REMOVE
from common.decorator import on_transaction_commit from common.decorator import on_transaction_commit
from common.signals import django_ready from common.signals import django_ready
@ -135,7 +136,7 @@ def _clear_users_from_org(org, users):
if not users: if not users:
return return
models = (AssetPermission, ApplicationPermission, UserGroup) models = (AssetPermission, ApplicationPermission, UserGroup, SystemUser)
for m in models: for m in models:
_remove_users(m, users, org) _remove_users(m, users, org)

View File

@ -5,7 +5,7 @@ from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orgs.mixins.serializers import BulkOrgResourceModelSerializer from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from perms.models import ApplicationPermission from perms.models import ApplicationPermission, Action
from ..base import ActionsField, BasePermissionSerializer from ..base import ActionsField, BasePermissionSerializer
__all__ = [ __all__ = [

View File

@ -1,6 +1,7 @@
from rest_framework import serializers from rest_framework import serializers
from perms.models import Action from perms.models import Action
from orgs.mixins.serializers import BulkOrgResourceModelSerializer from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from rest_framework.fields import empty
__all__ = ['ActionsDisplayField', 'ActionsField', 'BasePermissionSerializer'] __all__ = ['ActionsDisplayField', 'ActionsField', 'BasePermissionSerializer']
@ -10,6 +11,12 @@ class ActionsField(serializers.MultipleChoiceField):
kwargs['choices'] = Action.CHOICES kwargs['choices'] = Action.CHOICES
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def run_validation(self, data=empty):
data = super(ActionsField, self).run_validation()
if isinstance(data, list):
data = Action.choices_to_value(value=data)
return data
def to_representation(self, value): def to_representation(self, value):
return Action.value_to_choices(value) return Action.value_to_choices(value)

View File

@ -22,6 +22,7 @@ auditor_perms = user_perms + (
('terminal', 'sessionreplay', 'view,download', 'sessionreplay'), ('terminal', 'sessionreplay', 'view,download', 'sessionreplay'),
('terminal', 'session', '*', '*'), ('terminal', 'session', '*', '*'),
('terminal', 'command', '*', '*'), ('terminal', 'command', '*', '*'),
('ops', 'commandexecution', 'view', 'commandexecution')
) )

View File

@ -23,6 +23,10 @@ exclude_permissions = (
('common', 'setting', '*', '*'), ('common', 'setting', '*', '*'),
('authentication', 'privatetoken', '*', '*'), ('authentication', 'privatetoken', '*', '*'),
('authentication', 'accesskey', 'change,delete', 'accesskey'),
('authentication', 'connectiontoken', 'change,delete', 'connectiontoken'),
('authentication', 'ssotoken', 'change,delete', 'ssotoken'),
('authentication', 'superconnectiontoken', 'change,delete', 'superconnectiontoken'),
('users', 'userpasswordhistory', '*', '*'), ('users', 'userpasswordhistory', '*', '*'),
('applications', 'applicationuser', '*', '*'), ('applications', 'applicationuser', '*', '*'),
('applications', 'historicalaccount', '*', '*'), ('applications', 'historicalaccount', '*', '*'),
@ -56,10 +60,14 @@ exclude_permissions = (
('audits', 'passwordchangelog', 'add,change,delete', 'passwordchangelog'), ('audits', 'passwordchangelog', 'add,change,delete', 'passwordchangelog'),
('audits', 'userloginlog', 'add,change,delete,change', 'userloginlog'), ('audits', 'userloginlog', 'add,change,delete,change', 'userloginlog'),
('audits', 'ftplog', 'change,delete', 'ftplog'), ('audits', 'ftplog', 'change,delete', 'ftplog'),
('tickets', 'ticket', '*', '*'), ('tickets', 'ticketassignee', '*', 'ticketassignee'),
('tickets', 'ticketflow', 'add,delete', 'ticketflow'),
('tickets', 'comment', 'change,delete', 'comment'), ('tickets', 'comment', 'change,delete', 'comment'),
('tickets', 'ticket', 'delete', 'ticket'),
('tickets', 'ticketstep', '*', '*'), ('tickets', 'ticketstep', '*', '*'),
('tickets', 'ticketapprovalrule', '*', '*'), ('tickets', 'approvalrule', '*', '*'),
('tickets', 'superticket', 'delete', 'superticket'),
('tickets', 'ticketsession', 'delete', 'ticketsession'),
('xpack', 'interface', '*', '*'), ('xpack', 'interface', '*', '*'),
('xpack', 'license', '*', '*'), ('xpack', 'license', '*', '*'),
('xpack', 'syncinstancedetail', 'add,delete,change', 'syncinstancedetail'), ('xpack', 'syncinstancedetail', 'add,delete,change', 'syncinstancedetail'),
@ -75,13 +83,13 @@ exclude_permissions = (
only_system_permissions = ( only_system_permissions = (
('assets', 'platform', '*', '*'),
('users', 'user', 'delete', 'user'), ('users', 'user', 'delete', 'user'),
('rbac', 'role', 'delete,add,change', 'role'), ('rbac', 'role', 'delete,add,change', 'role'),
('rbac', 'systemrole', '*', '*'), ('rbac', 'systemrole', '*', '*'),
('rbac', 'rolebinding', '*', '*'), ('rbac', 'rolebinding', '*', '*'),
('rbac', 'systemrolebinding', '*', '*'), ('rbac', 'systemrolebinding', '*', '*'),
('rbac', 'orgrole', 'delete,add,change', '*'), ('rbac', 'orgrole', 'delete,add,change', '*'),
('rbac', 'orgrolebinding', 'delete,add,change', '*'),
('orgs', 'organization', '*', '*'), ('orgs', 'organization', '*', '*'),
('xpack', 'license', '*', '*'), ('xpack', 'license', '*', '*'),
('settings', 'setting', '*', '*'), ('settings', 'setting', '*', '*'),

View File

@ -12,6 +12,6 @@ class Migration(migrations.Migration):
operations = [ operations = [
migrations.AlterModelOptions( migrations.AlterModelOptions(
name='menupermission', name='menupermission',
options={'default_permissions': [], 'permissions': [('view_dashboard', 'Can view resource statistics'), ('view_console', 'Can view console view'), ('view_audit', 'Can view audit view'), ('view_workspace', 'Can view workspace view'), ('view_webterminal', 'Can view web terminal'), ('view_filemanager', 'Can view file manager')], 'verbose_name': 'Menu permission'}, options={'default_permissions': [], 'permissions': [('view_console', 'Can view console view'), ('view_audit', 'Can view audit view'), ('view_workspace', 'Can view workspace view'), ('view_webterminal', 'Can view web terminal'), ('view_filemanager', 'Can view file manager')], 'verbose_name': 'Menu permission'},
), ),
] ]

View File

@ -12,6 +12,6 @@ class Migration(migrations.Migration):
operations = [ operations = [
migrations.AlterModelOptions( migrations.AlterModelOptions(
name='menupermission', name='menupermission',
options={'default_permissions': [], 'permissions': [('view_console', 'Can view console view'), ('view_audit', 'Can view audit view'), ('view_workspace', 'Can view workspace view'), ('view_webterminal', 'Can view web terminal'), ('view_filemanager', 'Can view file manager'), ('view_dashboard', 'Can view dashboard')], 'verbose_name': 'Menu permission'}, options={'default_permissions': [], 'permissions': [('view_console', 'Can view console view'), ('view_audit', 'Can view audit view'), ('view_workspace', 'Can view workspace view'), ('view_webterminal', 'Can view web terminal'), ('view_filemanager', 'Can view file manager') ], 'verbose_name': 'Menu permission'},
), ),
] ]

View File

@ -17,5 +17,4 @@ class MenuPermission(models.Model):
('view_workspace', _('Can view workspace view')), ('view_workspace', _('Can view workspace view')),
('view_webterminal', _('Can view web terminal')), ('view_webterminal', _('Can view web terminal')),
('view_filemanager', _('Can view file manager')), ('view_filemanager', _('Can view file manager')),
('view_dashboard', _('Can view dashboard')),
] ]

View File

@ -98,7 +98,14 @@ special_pid_mapper = {
"perms.view_mydatabaseapp": "my_apps", "perms.view_mydatabaseapp": "my_apps",
"perms.connect_mydatabaseapp": "my_apps", "perms.connect_mydatabaseapp": "my_apps",
"xpack.interface": "view_setting", "xpack.interface": "view_setting",
"settings.change_terminal": "terminal_node" "settings.change_terminal": "terminal_node",
"settings.view_setting": "view_setting",
"settings.change_setting": "view_setting",
"rbac.view_console": "view_console",
"rbac.view_audit": "view_audit",
"rbac.view_workspace": "view_workspace",
"rbac.view_webterminal": "view_workspace",
"rbac.view_filemanager": "view_workspace",
} }
verbose_name_mapper = { verbose_name_mapper = {
@ -115,6 +122,32 @@ xpack_nodes = [
] ]
def _sort_action(node):
value = 0
if 'view' in node.title:
value += 2
elif 'add' in node.title:
value += 4
elif 'change' in node.title:
value += 6
elif 'delete' in node.title:
value += 8
else:
value += 10
return value
def sort_nodes(node):
value = 0
if node.isParent:
value += 50
else:
value += _sort_action(node)
return value
class PermissionTreeUtil: class PermissionTreeUtil:
get_permissions: Callable get_permissions: Callable
@ -122,7 +155,7 @@ class PermissionTreeUtil:
self.permissions = self.prefetch_permissions(permissions) self.permissions = self.prefetch_permissions(permissions)
self.all_permissions = self.prefetch_permissions( self.all_permissions = self.prefetch_permissions(
Permission.get_permissions(scope) Permission.get_permissions(scope)
).order_by('-codename') )
self.check_disabled = check_disabled self.check_disabled = check_disabled
self.total_counts = defaultdict(int) self.total_counts = defaultdict(int)
self.checked_counts = defaultdict(int) self.checked_counts = defaultdict(int)
@ -272,7 +305,7 @@ class PermissionTreeUtil:
# name 要特殊处理,解决 i18n 问题 # name 要特殊处理,解决 i18n 问题
name = self._get_permission_name(p, content_types_name_mapper) name = self._get_permission_name(p, content_types_name_mapper)
if settings.DEBUG: if settings.DEBUG:
name += '({})'.format(p.app_label_codename) name += '[{}]'.format(p.app_label_codename)
title = p.app_label_codename title = p.app_label_codename
pid = model_id pid = model_id
@ -320,9 +353,12 @@ class PermissionTreeUtil:
}, },
**data **data
} }
if not node_data.get('title'): node_data['title'] = node_data['id']
node_data['title'] = node_data['name']
node = TreeNode(**node_data) node = TreeNode(**node_data)
if settings.DEBUG:
node.name += ('[' + node.id + ']')
if settings.DEBUG:
node.name += ('-' + node.id)
node.name += f'({checked_count}/{total_count})' node.name += f'({checked_count}/{total_count})'
return node return node
@ -367,12 +403,12 @@ class PermissionTreeUtil:
return nodes return nodes
def create_tree_nodes(self): def create_tree_nodes(self):
nodes = [self._create_root_tree_node()] nodes = self._create_perms_nodes()
perms_nodes = self._create_perms_nodes() nodes += self._create_models_nodes()
models_nodes = self._create_models_nodes() nodes += self.create_apps_nodes()
apps_nodes = self.create_apps_nodes() nodes += self._create_extra_nodes()
extra_nodes = self._create_extra_nodes() nodes += self._create_views_node()
views_nodes = self._create_views_node() nodes += [self._create_root_tree_node()]
nodes += views_nodes + apps_nodes + models_nodes + perms_nodes + extra_nodes nodes.sort(key=sort_nodes)
return nodes return nodes

View File

@ -11,6 +11,9 @@ from .. import serializers
class DingTalkTestingAPI(GenericAPIView): class DingTalkTestingAPI(GenericAPIView):
serializer_class = serializers.DingTalkSettingSerializer serializer_class = serializers.DingTalkSettingSerializer
rbac_perms = {
'POST': 'settings.change_auth'
}
def post(self, request): def post(self, request):
serializer = self.serializer_class(data=request.data) serializer = self.serializer_class(data=request.data)

View File

@ -43,7 +43,6 @@ class PublicSettingApi(generics.RetrieveAPIView):
"XPACK_LICENSE_INFO": get_xpack_license_info(), "XPACK_LICENSE_INFO": get_xpack_license_info(),
"LOGIN_TITLE": self.get_login_title(), "LOGIN_TITLE": self.get_login_title(),
"LOGO_URLS": self.get_logo_urls(), "LOGO_URLS": self.get_logo_urls(),
"TICKETS_ENABLED": settings.TICKETS_ENABLED,
"PASSWORD_RULE": { "PASSWORD_RULE": {
'SECURITY_PASSWORD_MIN_LENGTH': settings.SECURITY_PASSWORD_MIN_LENGTH, 'SECURITY_PASSWORD_MIN_LENGTH': settings.SECURITY_PASSWORD_MIN_LENGTH,
'SECURITY_ADMIN_USER_PASSWORD_MIN_LENGTH': settings.SECURITY_ADMIN_USER_PASSWORD_MIN_LENGTH, 'SECURITY_ADMIN_USER_PASSWORD_MIN_LENGTH': settings.SECURITY_ADMIN_USER_PASSWORD_MIN_LENGTH,

View File

@ -41,9 +41,41 @@ class SettingsApi(generics.RetrieveUpdateAPIView):
'tencent': serializers.TencentSMSSettingSerializer, 'tencent': serializers.TencentSMSSettingSerializer,
} }
rbac_category_permissions = {
# 'all': 'view_setting',
'basic': 'view_setting',
'terminal': 'change_terminal',
'security': 'change_security',
'ldap': 'change_auth',
'email': 'change_email',
'email_content': 'change_email',
'wecom': 'change_auth',
'dingtalk': 'change_auth',
'feishu': 'change_auth',
'auth': 'change_auth',
'oidc': 'change_auth',
'keycloak': 'change_auth',
'radius': 'change_auth',
'cas': 'change_auth',
'sso': 'change_auth',
'saml2': 'change_auth',
'clean': 'change_clean',
'other': 'change_other',
'sms': 'change_sms',
'alibaba': 'change_sms',
'tencent': 'change_sms',
}
def get_queryset(self): def get_queryset(self):
return Setting.objects.all() return Setting.objects.all()
def check_permissions(self, request):
category = request.query_params.get('category', 'basic')
require_perm = self.rbac_category_permissions.get(category)
if not request.user.has_perm(require_perm):
self.permission_denied(request)
return super().check_permissions(request)
def get_serializer_class(self): def get_serializer_class(self):
category = self.request.query_params.get('category', 'basic') category = self.request.query_params.get('category', 'basic')
default = serializers.BasicSettingSerializer default = serializers.BasicSettingSerializer

View File

@ -12,6 +12,6 @@ class Migration(migrations.Migration):
operations = [ operations = [
migrations.AlterModelOptions( migrations.AlterModelOptions(
name='setting', name='setting',
options={'permissions': [('change_basic', 'Can change basic setting'), ('change_email', 'Can change email setting'), ('change_auth', 'Can change auth setting'), ('change_sms', 'Can change sms setting'), ('change_security', 'Can change security setting'), ('change_clean', 'Can change clean setting'), ('change_other', 'Can change other setting'), ('change_terminal_basic_setting', 'Can change terminal basic setting')], 'verbose_name': 'System setting'}, options={'permissions': [('change_email', 'Can change email setting'), ('change_auth', 'Can change auth setting'), ('change_systemmsgsubscription', 'Can sys msg sub setting'), ('change_sms', 'Can change sms setting'), ('change_security', 'Can change security setting'), ('change_clean', 'Can change clean setting'), ('change_interface', 'Can change interface setting'), ('change_license', 'Can change license setting'), ('change_terminal', 'Can change terminal setting'), ('change_other', 'Can change other setting')], 'verbose_name': 'System setting'},
), ),
] ]

View File

@ -1,17 +0,0 @@
# Generated by Django 3.1.14 on 2022-03-10 11:52
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('settings', '0005_auto_20220310_0616'),
]
operations = [
migrations.AlterModelOptions(
name='setting',
options={'permissions': [('change_basic', 'Can change basic setting'), ('change_email', 'Can change email setting'), ('change_auth', 'Can change auth setting'), ('change_sms', 'Can change sms setting'), ('change_security', 'Can change security setting'), ('change_clean', 'Can change clean setting'), ('change_other', 'Can change other setting'), ('change_interface', 'Can change interface setting'), ('change_license', 'Can change license setting'), ('change_terminal_basic_setting', 'Can change terminal basic setting')], 'verbose_name': 'System setting'},
),
]

View File

@ -1,17 +0,0 @@
# Generated by Django 3.1.14 on 2022-03-10 12:06
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('settings', '0006_auto_20220310_1952'),
]
operations = [
migrations.AlterModelOptions(
name='setting',
options={'permissions': [('change_basic', 'Can change basic setting'), ('change_email', 'Can change email setting'), ('change_auth', 'Can change auth setting'), ('change_sys_msg_sub', 'Can sys msg sub setting'), ('change_sms', 'Can change sms setting'), ('change_security', 'Can change security setting'), ('change_clean', 'Can change clean setting'), ('change_interface', 'Can change interface setting'), ('change_license', 'Can change license setting'), ('change_terminal', 'Can change terminal setting'), ('change_other', 'Can change other setting')], 'verbose_name': 'System setting'},
),
]

View File

@ -139,7 +139,6 @@ class Setting(models.Model):
db_table = "settings_setting" db_table = "settings_setting"
verbose_name = _("System setting") verbose_name = _("System setting")
permissions = [ permissions = [
('change_basic', _('Can change basic setting')),
('change_email', _('Can change email setting')), ('change_email', _('Can change email setting')),
('change_auth', _('Can change auth setting')), ('change_auth', _('Can change auth setting')),
('change_systemmsgsubscription', _('Can sys msg sub setting')), ('change_systemmsgsubscription', _('Can sys msg sub setting')),

View File

@ -41,7 +41,6 @@ class BasicSettingSerializer(serializers.Serializer):
required=False, max_length=1024, allow_blank=True, allow_null=True, label=_("Global organization name"), required=False, max_length=1024, allow_blank=True, allow_null=True, label=_("Global organization name"),
help_text=_('The name of global organization to display') help_text=_('The name of global organization to display')
) )
TICKETS_ENABLED = serializers.BooleanField(required=False, default=True, label=_("Enable tickets"))
ANNOUNCEMENT_ENABLED = serializers.BooleanField(label=_('Enable announcement'), default=True) ANNOUNCEMENT_ENABLED = serializers.BooleanField(label=_('Enable announcement'), default=True)
ANNOUNCEMENT = AnnouncementSerializer(label=_("Announcement")) ANNOUNCEMENT = AnnouncementSerializer(label=_("Announcement"))

View File

@ -4,6 +4,7 @@
from rest_framework import viewsets, mixins from rest_framework import viewsets, mixins
from common.exceptions import JMSException from common.exceptions import JMSException
from common.utils import lazyproperty from common.utils import lazyproperty
from rbac.permissions import RBACPermission
from tickets import serializers from tickets import serializers
from tickets.models import Ticket from tickets.models import Ticket
from tickets.permissions.comment import IsAssignee, IsApplicant, IsSwagger from tickets.permissions.comment import IsAssignee, IsApplicant, IsSwagger
@ -14,7 +15,7 @@ __all__ = ['CommentViewSet']
class CommentViewSet(mixins.CreateModelMixin, viewsets.ReadOnlyModelViewSet): class CommentViewSet(mixins.CreateModelMixin, viewsets.ReadOnlyModelViewSet):
serializer_class = serializers.CommentSerializer serializer_class = serializers.CommentSerializer
permission_classes = (IsSwagger | IsAssignee | IsApplicant,) permission_classes = (RBACPermission| IsSwagger | IsAssignee | IsApplicant)
@lazyproperty @lazyproperty
def ticket(self): def ticket(self):

View File

@ -19,7 +19,6 @@ __all__ = ['TicketViewSet', 'TicketFlowViewSet']
class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet): class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet):
permission_classes = (IsValidUser,)
serializer_class = serializers.TicketDisplaySerializer serializer_class = serializers.TicketDisplaySerializer
serializer_classes = { serializer_classes = {
'open': serializers.TicketApplySerializer, 'open': serializers.TicketApplySerializer,

View File

@ -5,12 +5,13 @@ from django.db.models import F
from common.drf.api import JMSBulkRelationModelViewSet from common.drf.api import JMSBulkRelationModelViewSet
from .. import serializers from .. import serializers
from ..models import User from ..models import User, UserGroup
__all__ = ['UserUserGroupRelationViewSet'] __all__ = ['UserUserGroupRelationViewSet']
class UserUserGroupRelationViewSet(JMSBulkRelationModelViewSet): class UserUserGroupRelationViewSet(JMSBulkRelationModelViewSet):
perm_model = UserGroup
filterset_fields = ('user', 'usergroup') filterset_fields = ('user', 'usergroup')
search_fields = filterset_fields search_fields = filterset_fields
serializer_class = serializers.UserUserGroupRelationSerializer serializer_class = serializers.UserUserGroupRelationSerializer

View File

@ -53,9 +53,11 @@ def clean_db_content_types():
('applications', 'remoteapp', 'view_remoteapp'), ('applications', 'remoteapp', 'view_remoteapp'),
('settings', 'setting', 'change_terminal_basic_setting'), ('settings', 'setting', 'change_terminal_basic_setting'),
('rbac', 'menupermission', 'view_resourcestatistics'), ('settings', 'setting', 'change_sys_msg_sub'),
('settings', 'setting', 'change_basic'),
('rbac', 'menupermission', 'view_userview'),
('rbac', 'menupermission', 'view_adminview'),
('rbac', 'menupermission', 'view_auditview'),
] ]
for app, model, codename in permissions_delete_required: for app, model, codename in permissions_delete_required:
print('delete {}.{} ({})'.format(app, codename, model)) print('delete {}.{} ({})'.format(app, codename, model))