diff --git a/apps/assets/api/asset.py b/apps/assets/api/asset.py index e2ce1b62a..a4701add9 100644 --- a/apps/assets/api/asset.py +++ b/apps/assets/api/asset.py @@ -48,7 +48,7 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet): return node = get_object_or_404(Node, id=node_id) - show_current_asset = self.request.query_params.get("show_current_asset") + show_current_asset = self.request.query_params.get("show_current_asset") in ('1', 'true') if node.is_root(): if show_current_asset: diff --git a/apps/assets/forms/domain.py b/apps/assets/forms/domain.py index 1b005ec2f..66f490bad 100644 --- a/apps/assets/forms/domain.py +++ b/apps/assets/forms/domain.py @@ -36,6 +36,10 @@ class DomainForm(forms.ModelForm): class GatewayForm(PasswordAndKeyAuthForm, OrgModelForm): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + password_field = self.fields.get('password') + password_field.help_text = _('Password should not contain special characters') def save(self, commit=True): # Because we define custom field, so we need rewrite :method: `save` diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index 97bcd4b47..b554c5c3a 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -6,8 +6,10 @@ import uuid import logging import random from functools import reduce +from collections import defaultdict from django.db import models +from django.db.models import Q from django.utils.translation import ugettext_lazy as _ from django.core.cache import cache @@ -100,6 +102,7 @@ class Asset(OrgModelMixin): verbose_name=_('CPU model')) cpu_count = models.IntegerField(null=True, verbose_name=_('CPU count')) cpu_cores = models.IntegerField(null=True, verbose_name=_('CPU cores')) + cpu_vcpus = models.IntegerField(null=True, verbose_name=_('CPU vcpus')) memory = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Memory')) disk_total = models.CharField(max_length=1024, null=True, blank=True, @@ -161,17 +164,25 @@ class Asset(OrgModelMixin): nodes = list(reduce(lambda x, y: set(x) | set(y), nodes)) return nodes - @property - def org_name(self): - from orgs.models import Organization - org = Organization.get_instance(self.org_id) - return org.name + @classmethod + def get_queryset_by_fullname_list(cls, fullname_list): + org_fullname_map = defaultdict(list) + for fullname in fullname_list: + hostname, org = cls.split_fullname(fullname) + org_fullname_map[org].append(hostname) + filter_arg = Q() + for org, hosts in org_fullname_map.items(): + if org.is_real(): + filter_arg |= Q(hostname__in=hosts, org_id=org.id) + else: + filter_arg |= Q(Q(org_id__isnull=True) | Q(org_id=''), hostname__in=hosts) + return Asset.objects.filter(filter_arg) @property def hardware_info(self): if self.cpu_count: return '{} Core {} {}'.format( - self.cpu_count * self.cpu_cores, + self.cpu_vcpus or self.cpu_count * self.cpu_cores, self.memory, self.disk_total ) else: diff --git a/apps/assets/models/label.py b/apps/assets/models/label.py index 7f1d08fa1..abc71e694 100644 --- a/apps/assets/models/label.py +++ b/apps/assets/models/label.py @@ -35,4 +35,4 @@ class Label(OrgModelMixin): class Meta: db_table = "assets_label" - unique_together = [('name', 'value')] + unique_together = [('name', 'value', 'org_id')] diff --git a/apps/assets/tasks.py b/apps/assets/tasks.py index f1773df11..463c3006a 100644 --- a/apps/assets/tasks.py +++ b/apps/assets/tasks.py @@ -44,7 +44,7 @@ def set_assets_hardware_info(result, **kwargs): logger.error("Get asset info failed: {}".format(hostname)) continue - asset = get_object_or_none(Asset, hostname=hostname) + asset = Asset.objects.get_object_by_fullname(hostname) if not asset: continue @@ -60,6 +60,7 @@ def set_assets_hardware_info(result, **kwargs): ___cpu_model = ___cpu_model[:64] ___cpu_count = info.get('ansible_processor_count', 0) ___cpu_cores = info.get('ansible_processor_cores', None) or len(info.get('ansible_processor', [])) + ___cpu_vcpus = info.get('ansible_processor_vcpus', 0) ___memory = '%s %s' % capacity_convert('{} MB'.format(info.get('ansible_memtotal_mb'))) disk_info = {} for dev, dev_info in info.get('ansible_devices', {}).items(): @@ -95,7 +96,7 @@ def update_assets_hardware_info_util(assets, task_name=None): # task_name = _("Update some assets hardware info") task_name = _("更新资产硬件信息") tasks = const.UPDATE_ASSETS_HARDWARE_TASKS - hostname_list = [asset.hostname for asset in assets if asset.is_active and asset.is_unixlike()] + hostname_list = [asset.fullname for asset in assets if asset.is_active and asset.is_unixlike()] if not hostname_list: logger.info("Not hosts get, may be asset is not active or not unixlike platform") return {} @@ -134,7 +135,7 @@ def update_assets_hardware_info_period(): # task_name = _("Update assets hardware info period") task_name = _("定期更新资产硬件信息") hostname_list = [ - asset.hostname for asset in Asset.objects.all() + asset.fullname for asset in Asset.objects.all() if asset.is_active and asset.is_unixlike() ] tasks = const.UPDATE_ASSETS_HARDWARE_TASKS @@ -181,7 +182,7 @@ def test_admin_user_connectability_util(admin_user, task_name): from ops.utils import update_or_create_ansible_task assets = admin_user.get_related_assets() - hosts = [asset.hostname for asset in assets + hosts = [asset.fullname for asset in assets if asset.is_active and asset.is_unixlike()] if not hosts: return @@ -228,7 +229,7 @@ def test_asset_connectability_util(assets, task_name=None): if task_name is None: # task_name = _("Test assets connectability") task_name = _("测试资产可连接性") - hosts = [asset.hostname for asset in assets if asset.is_active and asset.is_unixlike()] + hosts = [asset.fullname for asset in assets if asset.is_active and asset.is_unixlike()] if not hosts: logger.info("No hosts, passed") return {} @@ -280,7 +281,7 @@ def test_system_user_connectability_util(system_user, task_name): """ from ops.utils import update_or_create_ansible_task assets = system_user.get_assets() - hosts = [asset.hostname for asset in assets if asset.is_active and asset.is_unixlike()] + hosts = [asset.fullname for asset in assets if asset.is_active and asset.is_unixlike()] tasks = const.TEST_SYSTEM_USER_CONN_TASKS if not hosts: logger.info("No hosts, passed") @@ -378,7 +379,7 @@ def push_system_user_util(system_users, assets, task_name): logger.info("Not tasks, passed") return {} - hosts = [asset.hostname for asset in assets if asset.is_active and asset.is_unixlike()] + hosts = [asset.fullname for asset in assets if asset.is_active and asset.is_unixlike()] if not hosts: logger.info("Not hosts, passed") return {} diff --git a/apps/assets/templates/assets/asset_list.html b/apps/assets/templates/assets/asset_list.html index 3e1a8cfcd..79c34c043 100644 --- a/apps/assets/templates/assets/asset_list.html +++ b/apps/assets/templates/assets/asset_list.html @@ -451,10 +451,11 @@ $(document).ready(function(){ $.each(rows, function (index, obj) { assets.push(obj.id) }); + var _node_id = current_node ? current_node : null; $.ajax({ url: "{% url "assets:asset-export" %}", method: 'POST', - data: JSON.stringify({assets_id: assets, node_id: current_node.node_id}), + data: JSON.stringify({assets_id: assets, node_id: _node_id}), dataType: "json", success: function (data, textStatus) { window.open(data.redirect) diff --git a/apps/assets/templates/assets/asset_update.html b/apps/assets/templates/assets/asset_update.html index 7ed1da05a..caff4286d 100644 --- a/apps/assets/templates/assets/asset_update.html +++ b/apps/assets/templates/assets/asset_update.html @@ -12,7 +12,7 @@ {% block form %}
- {% if form.no_field_errors %} + {% if form.non_field_errors %}
{{ form.non_field_errors }}
diff --git a/apps/assets/utils.py b/apps/assets/utils.py index f841e4a79..9cee82a46 100644 --- a/apps/assets/utils.py +++ b/apps/assets/utils.py @@ -12,8 +12,8 @@ def get_assets_by_id_list(id_list): return Asset.objects.filter(id__in=id_list) -def get_assets_by_hostname_list(hostname_list): - return Asset.objects.filter(hostname__in=hostname_list) +def get_assets_by_fullname_list(hostname_list): + return Asset.objects.get_queryset_by_fullname_list(hostname_list) def get_system_user_by_name(name): diff --git a/apps/assets/views/asset.py b/apps/assets/views/asset.py index eb9972e94..c36ad9cc9 100644 --- a/apps/assets/views/asset.py +++ b/apps/assets/views/asset.py @@ -214,13 +214,13 @@ class AssetExportView(LoginRequiredMixin, View): def post(self, request, *args, **kwargs): try: assets_id = json.loads(request.body).get('assets_id', []) - assets_node_id = json.loads(request.body).get('node_id', None) + node_id = json.loads(request.body).get('node_id', None) except ValueError: return HttpResponse('Json object not valid', status=400) - if not assets_id and assets_node_id: - assets_node = get_object_or_none(Node, id=assets_node_id) - assets = assets_node.get_all_assets() + if not assets_id: + node = get_object_or_none(Node, id=node_id) if node_id else Node.root() + assets = node.get_all_assets() for asset in assets: assets_id.append(asset.id) diff --git a/apps/i18n/zh/LC_MESSAGES/django.mo b/apps/i18n/zh/LC_MESSAGES/django.mo index 97879c6d0..a7bbb98a3 100644 Binary files a/apps/i18n/zh/LC_MESSAGES/django.mo and b/apps/i18n/zh/LC_MESSAGES/django.mo differ diff --git a/apps/i18n/zh/LC_MESSAGES/django.po b/apps/i18n/zh/LC_MESSAGES/django.po index 1e4c69daa..335c491e0 100644 --- a/apps/i18n/zh/LC_MESSAGES/django.po +++ b/apps/i18n/zh/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Jumpserver 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-08-13 15:01+0800\n" +"POT-Creation-Date: 2018-08-15 15:14+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: Jumpserver team\n" @@ -103,7 +103,7 @@ msgid "Port" msgstr "端口" #: assets/forms/domain.py:15 assets/forms/label.py:13 -#: assets/models/asset.py:231 assets/templates/assets/admin_user_list.html:25 +#: assets/models/asset.py:232 assets/templates/assets/admin_user_list.html:25 #: assets/templates/assets/domain_detail.html:60 #: assets/templates/assets/domain_list.html:23 #: assets/templates/assets/label_list.html:16 @@ -123,7 +123,11 @@ msgstr "端口" msgid "Asset" msgstr "资产" -#: assets/forms/domain.py:55 assets/forms/user.py:79 assets/forms/user.py:139 +#: assets/forms/domain.py:42 +msgid "Password should not contain special characters" +msgstr "密码不能包含特殊字符" + +#: assets/forms/domain.py:59 assets/forms/user.py:79 assets/forms/user.py:139 #: assets/models/base.py:22 assets/models/cluster.py:18 #: assets/models/domain.py:18 assets/models/group.py:20 #: assets/models/label.py:18 assets/templates/assets/admin_user_detail.html:56 @@ -156,7 +160,7 @@ msgstr "资产" msgid "Name" msgstr "名称" -#: assets/forms/domain.py:56 assets/forms/user.py:80 assets/forms/user.py:140 +#: assets/forms/domain.py:60 assets/forms/user.py:80 assets/forms/user.py:140 #: assets/models/base.py:23 assets/templates/assets/admin_user_detail.html:60 #: assets/templates/assets/admin_user_list.html:24 #: assets/templates/assets/domain_gateway_list.html:60 @@ -303,42 +307,48 @@ msgstr "CPU数量" msgid "CPU cores" msgstr "CPU核数" -#: assets/models/asset.py:104 assets/templates/assets/asset_detail.html:89 +#: assets/models/asset.py:103 +#, fuzzy +#| msgid "CPU count" +msgid "CPU vcpus" +msgstr "CPU数量" + +#: assets/models/asset.py:105 assets/templates/assets/asset_detail.html:89 msgid "Memory" msgstr "内存" -#: assets/models/asset.py:106 +#: assets/models/asset.py:107 msgid "Disk total" msgstr "硬盘大小" -#: assets/models/asset.py:108 +#: assets/models/asset.py:109 msgid "Disk info" msgstr "硬盘信息" -#: assets/models/asset.py:111 assets/templates/assets/asset_detail.html:101 +#: assets/models/asset.py:112 assets/templates/assets/asset_detail.html:101 #: assets/templates/assets/user_asset_list.html:166 msgid "OS" msgstr "操作系统" -#: assets/models/asset.py:113 +#: assets/models/asset.py:114 msgid "OS version" msgstr "系统版本" -#: assets/models/asset.py:115 +#: assets/models/asset.py:116 msgid "OS arch" msgstr "系统架构" -#: assets/models/asset.py:117 +#: assets/models/asset.py:118 msgid "Hostname raw" msgstr "主机名原始" -#: assets/models/asset.py:121 assets/templates/assets/asset_create.html:34 +#: assets/models/asset.py:122 assets/templates/assets/asset_create.html:34 #: assets/templates/assets/asset_detail.html:220 #: assets/templates/assets/asset_update.html:39 templates/_nav.html:27 msgid "Labels" msgstr "标签管理" -#: assets/models/asset.py:123 assets/models/base.py:30 +#: assets/models/asset.py:124 assets/models/base.py:30 #: assets/models/cluster.py:28 assets/models/group.py:21 #: assets/templates/assets/admin_user_detail.html:68 #: assets/templates/assets/asset_detail.html:117 @@ -350,7 +360,7 @@ msgstr "标签管理" msgid "Created by" msgstr "创建者" -#: assets/models/asset.py:126 assets/models/cluster.py:26 +#: assets/models/asset.py:127 assets/models/cluster.py:26 #: assets/models/domain.py:21 assets/models/group.py:22 #: assets/models/label.py:24 assets/templates/assets/admin_user_detail.html:64 #: assets/templates/assets/domain_detail.html:68 @@ -364,7 +374,7 @@ msgstr "创建者" msgid "Date created" msgstr "创建日期" -#: assets/models/asset.py:128 assets/models/base.py:27 +#: assets/models/asset.py:129 assets/models/base.py:27 #: assets/models/cluster.py:29 assets/models/domain.py:19 #: assets/models/domain.py:48 assets/models/group.py:23 #: assets/models/label.py:22 assets/templates/assets/admin_user_detail.html:72 @@ -474,7 +484,7 @@ msgstr "默认资产组" msgid "User" msgstr "用户" -#: assets/models/label.py:19 assets/models/node.py:19 +#: assets/models/label.py:19 assets/models/node.py:20 #: assets/templates/assets/label_list.html:15 common/models.py:27 msgid "Value" msgstr "值" @@ -483,7 +493,7 @@ msgstr "值" msgid "Category" msgstr "分类" -#: assets/models/node.py:18 +#: assets/models/node.py:19 msgid "Key" msgstr "" @@ -557,35 +567,35 @@ msgstr "系统用户" msgid "%(value)s is not an even number" msgstr "%(value)s is not an even number" -#: assets/tasks.py:96 assets/tasks.py:116 +#: assets/tasks.py:97 assets/tasks.py:117 msgid "更新资产硬件信息" msgstr "" -#: assets/tasks.py:135 +#: assets/tasks.py:136 msgid "定期更新资产硬件信息" msgstr "" -#: assets/tasks.py:213 +#: assets/tasks.py:214 msgid "定期测试管理账号可连接性: {}" msgstr "" -#: assets/tasks.py:220 +#: assets/tasks.py:221 msgid "测试管理行号可连接性: {}" msgstr "" -#: assets/tasks.py:230 +#: assets/tasks.py:231 msgid "测试资产可连接性" msgstr "" -#: assets/tasks.py:300 +#: assets/tasks.py:301 msgid "Test system user connectability: {}" msgstr "测试系统用户可连接性: {}" -#: assets/tasks.py:316 +#: assets/tasks.py:317 msgid "定期测试系统用户可连接性: {}" msgstr "" -#: assets/tasks.py:401 +#: assets/tasks.py:402 msgid "推送系统用户到入资产: {}" msgstr "" @@ -850,7 +860,7 @@ msgstr "选择节点" #: assets/templates/assets/admin_user_detail.html:100 #: assets/templates/assets/asset_detail.html:200 -#: assets/templates/assets/asset_list.html:630 +#: assets/templates/assets/asset_list.html:631 #: assets/templates/assets/system_user_detail.html:195 #: assets/templates/assets/system_user_list.html:139 templates/_modal.html:22 #: terminal/templates/terminal/session_detail.html:108 @@ -1052,7 +1062,7 @@ msgstr "重命名成功" msgid "Rename failed, do not change the root node name" msgstr "重命名失败,不可以更改根节点名称" -#: assets/templates/assets/asset_list.html:625 +#: assets/templates/assets/asset_list.html:626 #: assets/templates/assets/system_user_list.html:134 #: users/templates/users/user_detail.html:369 #: users/templates/users/user_detail.html:394 @@ -1063,20 +1073,20 @@ msgstr "重命名失败,不可以更改根节点名称" msgid "Are you sure?" msgstr "你确认吗?" -#: assets/templates/assets/asset_list.html:626 +#: assets/templates/assets/asset_list.html:627 msgid "This will delete the selected assets !!!" msgstr "删除选择资产" -#: assets/templates/assets/asset_list.html:634 +#: assets/templates/assets/asset_list.html:635 msgid "Asset Deleted." msgstr "已被删除" -#: assets/templates/assets/asset_list.html:635 -#: assets/templates/assets/asset_list.html:640 +#: assets/templates/assets/asset_list.html:636 +#: assets/templates/assets/asset_list.html:641 msgid "Asset Delete" msgstr "删除" -#: assets/templates/assets/asset_list.html:639 +#: assets/templates/assets/asset_list.html:640 msgid "Asset Deleting failed." msgstr "删除失败" diff --git a/apps/ops/inventory.py b/apps/ops/inventory.py index 7ed89e75c..117d961a9 100644 --- a/apps/ops/inventory.py +++ b/apps/ops/inventory.py @@ -2,7 +2,7 @@ # from .ansible.inventory import BaseInventory -from assets.utils import get_assets_by_hostname_list, get_system_user_by_name +from assets.utils import get_assets_by_fullname_list, get_system_user_by_name __all__ = [ 'JMSInventory' @@ -44,7 +44,7 @@ class JMSInventory(BaseInventory): super().__init__(host_list=host_list) def get_jms_assets(self): - assets = get_assets_by_hostname_list(self.hostname_list) + assets = get_assets_by_fullname_list(self.hostname_list) return assets def convert_to_ansible(self, asset, run_as_admin=False): diff --git a/apps/orgs/mixins.py b/apps/orgs/mixins.py index 0355b02f8..648611d50 100644 --- a/apps/orgs/mixins.py +++ b/apps/orgs/mixins.py @@ -8,7 +8,7 @@ from django.shortcuts import redirect from django.forms import ModelForm from django.http.response import HttpResponseForbidden -from common.utils import get_logger +from common.utils import get_logger, is_uuid from .utils import current_org, set_current_org, set_to_root_org from .models import Organization @@ -39,6 +39,25 @@ class OrgManager(models.Manager): tl.times += 1 return queryset + def filter_by_fullname(self, fullname, field=None): + ori_org = current_org + value, org = self.model.split_fullname(fullname) + set_current_org(org) + if not field: + if hasattr(self.model, 'name'): + field = 'name' + elif hasattr(self.model, 'hostname'): + field = 'hostname' + queryset = self.get_queryset().filter(**{field: value}) + set_current_org(ori_org) + return queryset + + def get_object_by_fullname(self, fullname, field=None): + queryset = self.filter_by_fullname(fullname, field=field) + if len(queryset) == 1: + return queryset[0] + return None + def all(self): if not current_org: msg = 'You can `objects.set_current_org(org).all()` then run it' @@ -57,11 +76,50 @@ class OrgModelMixin(models.Model): org_id = models.CharField(max_length=36, null=True, blank=True, default=None) objects = OrgManager() + sep = '@' + def save(self, *args, **kwargs): if current_org and current_org.is_real(): self.org_id = current_org.id return super().save(*args, **kwargs) + @classmethod + def split_fullname(cls, fullname, sep=None): + if not sep: + sep = cls.sep + index = fullname.rfind(sep) + if index == -1: + value = fullname + org = Organization.default() + else: + value = fullname[:index] + org = Organization.get_instance(fullname[index + 1:]) + return value, org + + @property + def org(self): + from orgs.models import Organization + org = Organization.get_instance(self.org_id) + return org + + @property + def org_name(self): + return self.org.name + + @property + def fullname(self, attr=None): + name = '' + if attr and hasattr(self, attr): + name = getattr(self, attr) + elif hasattr(self, 'name'): + name = self.name + elif hasattr(self, 'hostname'): + name = self.hostname + if self.org.is_real(): + return name + self.sep + self.org_name + else: + return name + class Meta: abstract = True diff --git a/apps/orgs/models.py b/apps/orgs/models.py index f9e866741..3ddd91f71 100644 --- a/apps/orgs/models.py +++ b/apps/orgs/models.py @@ -4,6 +4,8 @@ from django.db import models from django.core.cache import cache from django.utils.translation import ugettext_lazy as _ +from common.utils import is_uuid + class Organization(models.Model): id = models.UUIDField(default=uuid.uuid4, primary_key=True) @@ -15,19 +17,23 @@ class Organization(models.Model): comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment')) CACHE_PREFIX = 'JMS_ORG_{}' - ROOT_ID = 'ROOT' - DEFAULT_ID = 'DEFAULT' + ROOT_ID_NAME = 'ROOT' + DEFAULT_ID_NAME = 'DEFAULT' def __str__(self): return self.name def set_to_cache(self): - key = self.CACHE_PREFIX.format(self.id) - cache.set(key, self, 3600) + key_id = self.CACHE_PREFIX.format(self.id) + key_name = self.CACHE_PREFIX.format(self.name) + cache.set(key_id, self, 3600) + cache.set(key_name, self, 3600) def expire_cache(self): - key = self.CACHE_PREFIX.format(self.id) - cache.set(key, self, 1) + key_id = self.CACHE_PREFIX.format(self.id) + key_name = self.CACHE_PREFIX.format(self.name) + cache.delete(key_id) + cache.delete(key_name) @classmethod def get_instance_from_cache(cls, oid): @@ -35,18 +41,23 @@ class Organization(models.Model): return cache.get(key, None) @classmethod - def get_instance(cls, oid, default=True): - cached = cls.get_instance_from_cache(oid) + def get_instance(cls, id_or_name, default=True): + cached = cls.get_instance_from_cache(id_or_name) if cached: return cached - if oid == cls.DEFAULT_ID: + if not id_or_name: + return cls.default() if default else None + elif id_or_name == cls.DEFAULT_ID_NAME: return cls.default() - elif oid == cls.ROOT_ID: + elif id_or_name == cls.ROOT_ID_NAME: return cls.root() try: - org = cls.objects.get(id=oid) + if is_uuid(id_or_name): + org = cls.objects.get(id=id_or_name) + else: + org = cls.objects.get(name=id_or_name) org.set_to_cache() except cls.DoesNotExist: org = cls.default() if default else None @@ -56,6 +67,8 @@ class Organization(models.Model): from users.models import User if self.is_default(): users = User.objects.filter(orgs__isnull=True) + elif not self.is_real(): + users = User.objects.all() else: users = self.users.all() users = users.exclude(role=User.ROLE_APP) @@ -90,14 +103,20 @@ class Organization(models.Model): @classmethod def default(cls): - return cls(id=cls.DEFAULT_ID, name="Default") + return cls(id=cls.DEFAULT_ID_NAME, name=cls.DEFAULT_ID_NAME) @classmethod def root(cls): - return cls(id=cls.ROOT_ID, name='Root') + return cls(id=cls.ROOT_ID_NAME, name=cls.ROOT_ID_NAME) - def is_default(self): - if self.id is self.DEFAULT_ID: + def is_root(self): + if self.id is self.ROOT_ID_NAME: + return True + else: + return False + + def is_default(self): + if self.id is self.DEFAULT_ID_NAME: return True else: return False