mirror of https://github.com/jumpserver/jumpserver
perf: 修改 label 为 tag
parent
4034e2152c
commit
097a6c5c5f
|
@ -16,22 +16,31 @@ from rest_framework.settings import api_settings
|
||||||
from rest_framework.utils import html
|
from rest_framework.utils import html
|
||||||
|
|
||||||
from common.db.fields import EncryptMixin
|
from common.db.fields import EncryptMixin
|
||||||
from common.serializers.fields import EncryptedField, LabeledChoiceField, ObjectRelatedField, LabelRelatedField
|
from common.serializers.fields import (
|
||||||
|
EncryptedField,
|
||||||
|
LabeledChoiceField,
|
||||||
|
ObjectRelatedField,
|
||||||
|
LabelRelatedField,
|
||||||
|
)
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'BulkSerializerMixin', 'BulkListSerializerMixin',
|
"BulkSerializerMixin",
|
||||||
'CommonSerializerMixin', 'CommonBulkSerializerMixin',
|
"BulkListSerializerMixin",
|
||||||
'SecretReadableMixin', 'CommonModelSerializer',
|
"CommonSerializerMixin",
|
||||||
'CommonBulkModelSerializer', 'ResourceLabelsMixin',
|
"CommonBulkSerializerMixin",
|
||||||
|
"SecretReadableMixin",
|
||||||
|
"CommonModelSerializer",
|
||||||
|
"CommonBulkModelSerializer",
|
||||||
|
"ResourceLabelsMixin",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class SecretReadableMixin(serializers.Serializer):
|
class SecretReadableMixin(serializers.Serializer):
|
||||||
""" 加密字段 (EncryptedField) 可读性 """
|
"""加密字段 (EncryptedField) 可读性"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(SecretReadableMixin, self).__init__(*args, **kwargs)
|
super(SecretReadableMixin, self).__init__(*args, **kwargs)
|
||||||
if not hasattr(self, 'Meta') or not hasattr(self.Meta, 'extra_kwargs'):
|
if not hasattr(self, "Meta") or not hasattr(self.Meta, "extra_kwargs"):
|
||||||
return
|
return
|
||||||
extra_kwargs = self.Meta.extra_kwargs
|
extra_kwargs = self.Meta.extra_kwargs
|
||||||
for field_name, serializer_field in self.fields.items():
|
for field_name, serializer_field in self.fields.items():
|
||||||
|
@ -40,9 +49,9 @@ class SecretReadableMixin(serializers.Serializer):
|
||||||
if field_name not in extra_kwargs:
|
if field_name not in extra_kwargs:
|
||||||
continue
|
continue
|
||||||
field_extra_kwargs = extra_kwargs[field_name]
|
field_extra_kwargs = extra_kwargs[field_name]
|
||||||
if 'write_only' not in field_extra_kwargs:
|
if "write_only" not in field_extra_kwargs:
|
||||||
continue
|
continue
|
||||||
serializer_field.write_only = field_extra_kwargs['write_only']
|
serializer_field.write_only = field_extra_kwargs["write_only"]
|
||||||
|
|
||||||
|
|
||||||
class BulkSerializerMixin(object):
|
class BulkSerializerMixin(object):
|
||||||
|
@ -53,20 +62,25 @@ class BulkSerializerMixin(object):
|
||||||
|
|
||||||
def to_internal_value(self, data):
|
def to_internal_value(self, data):
|
||||||
from rest_framework_bulk import BulkListSerializer
|
from rest_framework_bulk import BulkListSerializer
|
||||||
|
|
||||||
ret = super(BulkSerializerMixin, self).to_internal_value(data)
|
ret = super(BulkSerializerMixin, self).to_internal_value(data)
|
||||||
|
|
||||||
id_attr = getattr(self.Meta, 'update_lookup_field', 'id')
|
id_attr = getattr(self.Meta, "update_lookup_field", "id")
|
||||||
if self.context.get('view'):
|
if self.context.get("view"):
|
||||||
request_method = getattr(getattr(self.context.get('view'), 'request'), 'method', '')
|
request_method = getattr(
|
||||||
|
getattr(self.context.get("view"), "request"), "method", ""
|
||||||
|
)
|
||||||
# add update_lookup_field field back to validated data
|
# add update_lookup_field field back to validated data
|
||||||
# since super by default strips out read-only fields
|
# since super by default strips out read-only fields
|
||||||
# hence id will no longer be present in validated_data
|
# hence id will no longer be present in validated_data
|
||||||
if all([
|
if all(
|
||||||
|
[
|
||||||
isinstance(self.root, BulkListSerializer),
|
isinstance(self.root, BulkListSerializer),
|
||||||
id_attr,
|
id_attr,
|
||||||
request_method in ('PUT', 'PATCH')
|
request_method in ("PUT", "PATCH"),
|
||||||
]):
|
]
|
||||||
id_field = self.fields.get("id") or self.fields.get('pk')
|
):
|
||||||
|
id_field = self.fields.get("id") or self.fields.get("pk")
|
||||||
if data.get("id"):
|
if data.get("id"):
|
||||||
id_value = id_field.to_internal_value(data.get("id"))
|
id_value = id_field.to_internal_value(data.get("id"))
|
||||||
else:
|
else:
|
||||||
|
@ -89,9 +103,10 @@ class BulkSerializerMixin(object):
|
||||||
@classmethod
|
@classmethod
|
||||||
def many_init(cls, *args, **kwargs):
|
def many_init(cls, *args, **kwargs):
|
||||||
from .common import AdaptedBulkListSerializer
|
from .common import AdaptedBulkListSerializer
|
||||||
meta = getattr(cls, 'Meta', None)
|
|
||||||
assert meta is not None, 'Must have `Meta`'
|
meta = getattr(cls, "Meta", None)
|
||||||
if not hasattr(meta, 'list_serializer_class'):
|
assert meta is not None, "Must have `Meta`"
|
||||||
|
if not hasattr(meta, "list_serializer_class"):
|
||||||
meta.list_serializer_class = AdaptedBulkListSerializer
|
meta.list_serializer_class = AdaptedBulkListSerializer
|
||||||
return super(BulkSerializerMixin, cls).many_init(*args, **kwargs)
|
return super(BulkSerializerMixin, cls).many_init(*args, **kwargs)
|
||||||
|
|
||||||
|
@ -115,21 +130,21 @@ class BulkListSerializerMixin:
|
||||||
data = html.parse_html_list(data)
|
data = html.parse_html_list(data)
|
||||||
|
|
||||||
if not isinstance(data, list):
|
if not isinstance(data, list):
|
||||||
message = self.error_messages['not_a_list'].format(
|
message = self.error_messages["not_a_list"].format(
|
||||||
input_type=type(data).__name__
|
input_type=type(data).__name__
|
||||||
)
|
)
|
||||||
raise ValidationError({
|
raise ValidationError(
|
||||||
api_settings.NON_FIELD_ERRORS_KEY: [message]
|
{api_settings.NON_FIELD_ERRORS_KEY: [message]}, code="not_a_list"
|
||||||
}, code='not_a_list')
|
)
|
||||||
|
|
||||||
if not self.allow_empty and len(data) == 0:
|
if not self.allow_empty and len(data) == 0:
|
||||||
if self.parent and self.partial:
|
if self.parent and self.partial:
|
||||||
raise SkipField()
|
raise SkipField()
|
||||||
|
|
||||||
message = self.error_messages['empty']
|
message = self.error_messages["empty"]
|
||||||
raise ValidationError({
|
raise ValidationError(
|
||||||
api_settings.NON_FIELD_ERRORS_KEY: [message]
|
{api_settings.NON_FIELD_ERRORS_KEY: [message]}, code="empty"
|
||||||
}, code='empty')
|
)
|
||||||
|
|
||||||
ret = []
|
ret = []
|
||||||
errors = []
|
errors = []
|
||||||
|
@ -137,9 +152,9 @@ class BulkListSerializerMixin:
|
||||||
for item in data:
|
for item in data:
|
||||||
try:
|
try:
|
||||||
# prepare child serializer to only handle one instance
|
# prepare child serializer to only handle one instance
|
||||||
if 'id' in item:
|
if "id" in item:
|
||||||
pk = item["id"]
|
pk = item["id"]
|
||||||
elif 'pk' in item:
|
elif "pk" in item:
|
||||||
pk = item["pk"]
|
pk = item["pk"]
|
||||||
else:
|
else:
|
||||||
raise ValidationError("id or pk not in data")
|
raise ValidationError("id or pk not in data")
|
||||||
|
@ -163,13 +178,13 @@ class BulkListSerializerMixin:
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
ModelClass = self.child.Meta.model
|
ModelClass = self.child.Meta.model
|
||||||
use_model_bulk_create = getattr(self.child.Meta, 'use_model_bulk_create', False)
|
use_model_bulk_create = getattr(self.child.Meta, "use_model_bulk_create", False)
|
||||||
model_bulk_create_kwargs = getattr(self.child.Meta, 'model_bulk_create_kwargs', {})
|
model_bulk_create_kwargs = getattr(
|
||||||
|
self.child.Meta, "model_bulk_create_kwargs", {}
|
||||||
|
)
|
||||||
|
|
||||||
if use_model_bulk_create:
|
if use_model_bulk_create:
|
||||||
to_create = [
|
to_create = [ModelClass(**attrs) for attrs in validated_data]
|
||||||
ModelClass(**attrs) for attrs in validated_data
|
|
||||||
]
|
|
||||||
objs = ModelClass._default_manager.bulk_create(
|
objs = ModelClass._default_manager.bulk_create(
|
||||||
to_create, **model_bulk_create_kwargs
|
to_create, **model_bulk_create_kwargs
|
||||||
)
|
)
|
||||||
|
@ -184,18 +199,18 @@ class BaseDynamicFieldsPlugin:
|
||||||
|
|
||||||
def can_dynamic(self):
|
def can_dynamic(self):
|
||||||
try:
|
try:
|
||||||
request = self.serializer.context['request']
|
request = self.serializer.context["request"]
|
||||||
method = request.method
|
method = request.method
|
||||||
except (AttributeError, TypeError, KeyError):
|
except (AttributeError, TypeError, KeyError):
|
||||||
# The serializer was not initialized with request context.
|
# The serializer was not initialized with request context.
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if method != 'GET':
|
if method != "GET":
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def get_request(self):
|
def get_request(self):
|
||||||
return self.serializer.context['request']
|
return self.serializer.context["request"]
|
||||||
|
|
||||||
def get_query_params(self):
|
def get_query_params(self):
|
||||||
request = self.get_request()
|
request = self.get_request()
|
||||||
|
@ -203,7 +218,7 @@ class BaseDynamicFieldsPlugin:
|
||||||
query_params = request.query_params
|
query_params = request.query_params
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# DRF 2
|
# DRF 2
|
||||||
query_params = getattr(request, 'QUERY_PARAMS', request.GET)
|
query_params = getattr(request, "QUERY_PARAMS", request.GET)
|
||||||
return query_params
|
return query_params
|
||||||
|
|
||||||
def get_exclude_field_names(self):
|
def get_exclude_field_names(self):
|
||||||
|
@ -214,20 +229,24 @@ class QueryFieldsMixin(BaseDynamicFieldsPlugin):
|
||||||
# https://github.com/wimglenn/djangorestframework-queryfields/
|
# https://github.com/wimglenn/djangorestframework-queryfields/
|
||||||
|
|
||||||
# If using Django filters in the API, these labels mustn't conflict with any model field names.
|
# If using Django filters in the API, these labels mustn't conflict with any model field names.
|
||||||
include_arg_name = 'fields'
|
include_arg_name = "fields"
|
||||||
exclude_arg_name = 'fields!'
|
exclude_arg_name = "fields!"
|
||||||
|
|
||||||
# Split field names by this string. It doesn't necessarily have to be a single character.
|
# Split field names by this string. It doesn't necessarily have to be a single character.
|
||||||
# Avoid RFC 1738 reserved characters i.e. ';', '/', '?', ':', '@', '=' and '&'
|
# Avoid RFC 1738 reserved characters i.e. ';', '/', '?', ':', '@', '=' and '&'
|
||||||
delimiter = ','
|
delimiter = ","
|
||||||
|
|
||||||
def get_exclude_field_names(self):
|
def get_exclude_field_names(self):
|
||||||
query_params = self.get_query_params()
|
query_params = self.get_query_params()
|
||||||
includes = query_params.getlist(self.include_arg_name)
|
includes = query_params.getlist(self.include_arg_name)
|
||||||
include_field_names = {name for names in includes for name in names.split(self.delimiter) if name}
|
include_field_names = {
|
||||||
|
name for names in includes for name in names.split(self.delimiter) if name
|
||||||
|
}
|
||||||
|
|
||||||
excludes = query_params.getlist(self.exclude_arg_name)
|
excludes = query_params.getlist(self.exclude_arg_name)
|
||||||
exclude_field_names = {name for names in excludes for name in names.split(self.delimiter) if name}
|
exclude_field_names = {
|
||||||
|
name for names in excludes for name in names.split(self.delimiter) if name
|
||||||
|
}
|
||||||
|
|
||||||
if not include_field_names and not exclude_field_names:
|
if not include_field_names and not exclude_field_names:
|
||||||
# No user fields filtering was requested, we have nothing to do here.
|
# No user fields filtering was requested, we have nothing to do here.
|
||||||
|
@ -242,10 +261,10 @@ class QueryFieldsMixin(BaseDynamicFieldsPlugin):
|
||||||
|
|
||||||
|
|
||||||
class SizedModelFieldsMixin(BaseDynamicFieldsPlugin):
|
class SizedModelFieldsMixin(BaseDynamicFieldsPlugin):
|
||||||
arg_name = 'fields_size'
|
arg_name = "fields_size"
|
||||||
|
|
||||||
def can_dynamic(self):
|
def can_dynamic(self):
|
||||||
if not hasattr(self.serializer, 'Meta'):
|
if not hasattr(self.serializer, "Meta"):
|
||||||
return False
|
return False
|
||||||
can = super().can_dynamic()
|
can = super().can_dynamic()
|
||||||
return can
|
return can
|
||||||
|
@ -255,9 +274,9 @@ class SizedModelFieldsMixin(BaseDynamicFieldsPlugin):
|
||||||
size = query_params.get(self.arg_name)
|
size = query_params.get(self.arg_name)
|
||||||
if not size:
|
if not size:
|
||||||
return []
|
return []
|
||||||
if size not in ['mini', 'small']:
|
if size not in ["mini", "small"]:
|
||||||
return []
|
return []
|
||||||
size_fields = getattr(self.serializer.Meta, 'fields_{}'.format(size), None)
|
size_fields = getattr(self.serializer.Meta, "fields_{}".format(size), None)
|
||||||
if not size_fields or not isinstance(size_fields, Iterable):
|
if not size_fields or not isinstance(size_fields, Iterable):
|
||||||
return []
|
return []
|
||||||
serializer_field_names = set(self.serializer.fields)
|
serializer_field_names = set(self.serializer.fields)
|
||||||
|
@ -269,7 +288,7 @@ class XPACKModelFieldsMixin(BaseDynamicFieldsPlugin):
|
||||||
def get_exclude_field_names(self):
|
def get_exclude_field_names(self):
|
||||||
if settings.XPACK_LICENSE_IS_VALID:
|
if settings.XPACK_LICENSE_IS_VALID:
|
||||||
return set()
|
return set()
|
||||||
fields_xpack = set(getattr(self.serializer.Meta, 'fields_xpack', set()))
|
fields_xpack = set(getattr(self.serializer.Meta, "fields_xpack", set()))
|
||||||
return fields_xpack
|
return fields_xpack
|
||||||
|
|
||||||
|
|
||||||
|
@ -279,9 +298,9 @@ class DefaultValueFieldsMixin:
|
||||||
self.set_fields_default_value()
|
self.set_fields_default_value()
|
||||||
|
|
||||||
def set_fields_default_value(self):
|
def set_fields_default_value(self):
|
||||||
if not hasattr(self, 'Meta'):
|
if not hasattr(self, "Meta"):
|
||||||
return
|
return
|
||||||
if not hasattr(self.Meta, 'model'):
|
if not hasattr(self.Meta, "model"):
|
||||||
return
|
return
|
||||||
model = self.Meta.model
|
model = self.Meta.model
|
||||||
|
|
||||||
|
@ -291,17 +310,19 @@ class DefaultValueFieldsMixin:
|
||||||
model_field = getattr(model, name, None)
|
model_field = getattr(model, name, None)
|
||||||
if model_field is None:
|
if model_field is None:
|
||||||
continue
|
continue
|
||||||
if not hasattr(model_field, 'field') \
|
if (
|
||||||
or not hasattr(model_field.field, 'default') \
|
not hasattr(model_field, "field")
|
||||||
or model_field.field.default == NOT_PROVIDED:
|
or not hasattr(model_field.field, "default")
|
||||||
|
or model_field.field.default == NOT_PROVIDED
|
||||||
|
):
|
||||||
continue
|
continue
|
||||||
if name == 'id':
|
if name == "id":
|
||||||
continue
|
continue
|
||||||
default = model_field.field.default
|
default = model_field.field.default
|
||||||
|
|
||||||
if callable(default):
|
if callable(default):
|
||||||
default = default()
|
default = default()
|
||||||
if default == '':
|
if default == "":
|
||||||
continue
|
continue
|
||||||
# print(f"Set default value: {name}: {default}")
|
# print(f"Set default value: {name}: {default}")
|
||||||
serializer_field.default = default
|
serializer_field.default = default
|
||||||
|
@ -311,7 +332,12 @@ class DynamicFieldsMixin:
|
||||||
"""
|
"""
|
||||||
可以控制显示不同的字段,mini 最少,small 不包含关系
|
可以控制显示不同的字段,mini 最少,small 不包含关系
|
||||||
"""
|
"""
|
||||||
dynamic_fields_plugins = [QueryFieldsMixin, SizedModelFieldsMixin, XPACKModelFieldsMixin]
|
|
||||||
|
dynamic_fields_plugins = [
|
||||||
|
QueryFieldsMixin,
|
||||||
|
SizedModelFieldsMixin,
|
||||||
|
XPACKModelFieldsMixin,
|
||||||
|
]
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
@ -336,12 +362,13 @@ class SomeFieldsMixin:
|
||||||
instance: None
|
instance: None
|
||||||
initial_data: dict
|
initial_data: dict
|
||||||
common_fields = (
|
common_fields = (
|
||||||
'comment', 'created_by', 'updated_by',
|
"comment",
|
||||||
'date_created', 'date_updated',
|
"created_by",
|
||||||
)
|
"updated_by",
|
||||||
secret_fields = (
|
"date_created",
|
||||||
'password', 'token', 'secret', 'key', 'private_key'
|
"date_updated",
|
||||||
)
|
)
|
||||||
|
secret_fields = ("password", "token", "secret", "key", "private_key")
|
||||||
|
|
||||||
def get_initial_value(self, attr, default=None):
|
def get_initial_value(self, attr, default=None):
|
||||||
value = self.initial_data.get(attr)
|
value = self.initial_data.get(attr)
|
||||||
|
@ -365,7 +392,7 @@ class SomeFieldsMixin:
|
||||||
bool_fields.append(to_add)
|
bool_fields.append(to_add)
|
||||||
elif isinstance(field, serializers.DateTimeField):
|
elif isinstance(field, serializers.DateTimeField):
|
||||||
datetime_fields.append(to_add)
|
datetime_fields.append(to_add)
|
||||||
elif name in ('comment', 'created_by', 'updated_by'):
|
elif name in ("comment", "created_by", "updated_by"):
|
||||||
common_fields.append(to_add)
|
common_fields.append(to_add)
|
||||||
else:
|
else:
|
||||||
other_fields.append(to_add)
|
other_fields.append(to_add)
|
||||||
|
@ -381,15 +408,19 @@ class SomeFieldsMixin:
|
||||||
secret_readable = isinstance(self, SecretReadableMixin)
|
secret_readable = isinstance(self, SecretReadableMixin)
|
||||||
|
|
||||||
for name, field in fields.items():
|
for name, field in fields.items():
|
||||||
if name == 'id':
|
if name == "id":
|
||||||
field.label = 'ID'
|
field.label = "ID"
|
||||||
elif isinstance(field, EncryptMixin) and not secret_readable:
|
elif isinstance(field, EncryptMixin) and not secret_readable:
|
||||||
field.write_only = True
|
field.write_only = True
|
||||||
return fields
|
return fields
|
||||||
|
|
||||||
|
|
||||||
class CommonSerializerMixin(DynamicFieldsMixin, RelatedModelSerializerMixin,
|
class CommonSerializerMixin(
|
||||||
SomeFieldsMixin, DefaultValueFieldsMixin):
|
DynamicFieldsMixin,
|
||||||
|
RelatedModelSerializerMixin,
|
||||||
|
SomeFieldsMixin,
|
||||||
|
DefaultValueFieldsMixin,
|
||||||
|
):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@ -406,17 +437,19 @@ class CommonBulkModelSerializer(CommonBulkSerializerMixin, serializers.ModelSeri
|
||||||
|
|
||||||
|
|
||||||
class ResourceLabelsMixin(serializers.Serializer):
|
class ResourceLabelsMixin(serializers.Serializer):
|
||||||
labels = LabelRelatedField(many=True, label=_('Labels'), required=False, allow_null=True, source='res_labels')
|
labels = LabelRelatedField(
|
||||||
|
many=True, label=_("Tags"), required=False, allow_null=True, source="res_labels"
|
||||||
|
)
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
labels = validated_data.pop('res_labels', None)
|
labels = validated_data.pop("res_labels", None)
|
||||||
res = super().update(instance, validated_data)
|
res = super().update(instance, validated_data)
|
||||||
if labels is not None:
|
if labels is not None:
|
||||||
instance.res_labels.set(labels, bulk=False)
|
instance.res_labels.set(labels, bulk=False)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
labels = validated_data.pop('res_labels', None)
|
labels = validated_data.pop("res_labels", None)
|
||||||
instance = super().create(validated_data)
|
instance = super().create(validated_data)
|
||||||
if labels is not None:
|
if labels is not None:
|
||||||
instance.res_labels.set(labels, bulk=False)
|
instance.res_labels.set(labels, bulk=False)
|
||||||
|
@ -424,4 +457,4 @@ class ResourceLabelsMixin(serializers.Serializer):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setup_eager_loading(cls, queryset):
|
def setup_eager_loading(cls, queryset):
|
||||||
return queryset.prefetch_related('labels')
|
return queryset.prefetch_related("labels")
|
||||||
|
|
|
@ -3974,7 +3974,7 @@ msgid "Resource ID"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: labels/models.py:41
|
#: labels/models.py:41
|
||||||
msgid "Labeled resource"
|
msgid "Tagged resource"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: labels/serializers.py:22
|
#: labels/serializers.py:22
|
||||||
|
|
|
@ -4107,7 +4107,7 @@ msgid "Resource ID"
|
||||||
msgstr "リソースID"
|
msgstr "リソースID"
|
||||||
|
|
||||||
#: labels/models.py:41
|
#: labels/models.py:41
|
||||||
msgid "Labeled resource"
|
msgid "Tagged resource"
|
||||||
msgstr "関連リソース"
|
msgstr "関連リソース"
|
||||||
|
|
||||||
#: labels/serializers.py:22
|
#: labels/serializers.py:22
|
||||||
|
|
|
@ -4041,7 +4041,7 @@ msgid "Resource ID"
|
||||||
msgstr "资源 ID"
|
msgstr "资源 ID"
|
||||||
|
|
||||||
#: labels/models.py:41
|
#: labels/models.py:41
|
||||||
msgid "Labeled resource"
|
msgid "Tagged resource"
|
||||||
msgstr "关联的资源"
|
msgstr "关联的资源"
|
||||||
|
|
||||||
#: labels/serializers.py:22
|
#: labels/serializers.py:22
|
||||||
|
|
|
@ -293,8 +293,8 @@
|
||||||
"ConfirmPassword": "Confirm password",
|
"ConfirmPassword": "Confirm password",
|
||||||
"ConnectAssets": "Connect assets",
|
"ConnectAssets": "Connect assets",
|
||||||
"ConnectMethod": "Connect method",
|
"ConnectMethod": "Connect method",
|
||||||
"ConnectMethodACLHelpMsg": "Connect methods can be filtered to control whether users can use a certain connect method to log in to the asset. according to your set rules, some connect methods can be allowed, while others can be prohibited (globally effective).",
|
"ConnectMethodACLHelpMsg": "Connect methods can be filtered to control whether users can use a certain connect method to login to the asset. according to your set rules, some connect methods can be allowed, while others can be prohibited (globally effective).",
|
||||||
"ConnectMethodACLHelpText": "Connect methods can be filtered to control whether users can use a certain connect method to log in to the asset. according to your set rules, some connect methods can be allowed, while others can be prohibited.",
|
"ConnectMethodACLHelpText": "Connect methods can be filtered to control whether users can use a certain connect method to login to the asset. according to your set rules, some connect methods can be allowed, while others can be prohibited.",
|
||||||
"ConnectMethodAclCreate": "Create connect method control",
|
"ConnectMethodAclCreate": "Create connect method control",
|
||||||
"ConnectMethodAclDetail": "Connect method control details",
|
"ConnectMethodAclDetail": "Connect method control details",
|
||||||
"ConnectMethodAclList": "Connect method",
|
"ConnectMethodAclList": "Connect method",
|
||||||
|
@ -424,9 +424,9 @@
|
||||||
"Enable": "Enable",
|
"Enable": "Enable",
|
||||||
"EnableKoKoSSHHelpText": "When switched on, connecting to the asset will display ssh client pull-up method",
|
"EnableKoKoSSHHelpText": "When switched on, connecting to the asset will display ssh client pull-up method",
|
||||||
"Endpoint": "Endpoint",
|
"Endpoint": "Endpoint",
|
||||||
"EndpointListHelpMessage": "The service endpoint is the address (port) for users to access the service. when users connect to assets, they choose service endpoints based on endpoint rules and asset labels, using them as access points to establish connections and achieve distributed connections to assets",
|
"EndpointListHelpMessage": "The service endpoint is the address (port) for users to access the service. when users connect to assets, they choose service endpoints based on endpoint rules and asset tags, using them as access points to establish connections and achieve distributed connections to assets",
|
||||||
"EndpointRule": "Endpoint rules",
|
"EndpointRule": "Endpoint rules",
|
||||||
"EndpointRuleListHelpMessage": "For the server endpoint selection strategy, there are currently two options:<br>1. specify the endpoint according to the endpoint rule (current page);<br>2. choose the endpoint through asset labels, with the fixed label name being 'endpoint' and the value being the name of the endpoint.<br>the tag matching method is preferred for both methods, as the ip range may conflict, and the tag method exists as a supplement to the rules.",
|
"EndpointRuleListHelpMessage": "For the server endpoint selection strategy, there are currently two options:<br>1. specify the endpoint according to the endpoint rule (current page);<br>2. choose the endpoint through asset tags, with the fixed tag name being 'endpoint' and the value being the name of the endpoint.<br>the tag matching method is preferred for both methods, as the ip range may conflict, and the tag method exists as a supplement to the rules.",
|
||||||
"Endswith": "Ending with...",
|
"Endswith": "Ending with...",
|
||||||
"EnsureThisValueIsGreaterThanOrEqualTo1": "Please make sure this number is greater than or equal to 1",
|
"EnsureThisValueIsGreaterThanOrEqualTo1": "Please make sure this number is greater than or equal to 1",
|
||||||
"EnterForSearch": "Press enter to search",
|
"EnterForSearch": "Press enter to search",
|
||||||
|
@ -607,12 +607,12 @@
|
||||||
"KingSoftCloud": "KingSoft Cloud",
|
"KingSoftCloud": "KingSoft Cloud",
|
||||||
"KokoSetting": "KoKo",
|
"KokoSetting": "KoKo",
|
||||||
"LDAPUser": "LDAP Users",
|
"LDAPUser": "LDAP Users",
|
||||||
"Label": "Label",
|
"Tag": "Tag",
|
||||||
"LAN": "LAN",
|
"LAN": "LAN",
|
||||||
"LabelCreate": "Create label",
|
"TagCreate": "Create tag",
|
||||||
"LabelInputFormatValidation": "Label format error, the correct format is: name:value",
|
"TagInputFormatValidation": "Tag format error, the correct format is: name:value",
|
||||||
"LabelList": "Labels",
|
"TagList": "Tags",
|
||||||
"LabelUpdate": "Update the label",
|
"TagUpdate": "Update the tag",
|
||||||
"Language": "Language",
|
"Language": "Language",
|
||||||
"LarkOAuth": "Lark OAuth",
|
"LarkOAuth": "Lark OAuth",
|
||||||
"Last30": "Recent 30 items",
|
"Last30": "Recent 30 items",
|
||||||
|
@ -652,7 +652,7 @@
|
||||||
"LoginLogTotal": "Total login logs",
|
"LoginLogTotal": "Total login logs",
|
||||||
"LoginNum": "Total login logs",
|
"LoginNum": "Total login logs",
|
||||||
"LoginPasswordSetting": "Login password",
|
"LoginPasswordSetting": "Login password",
|
||||||
"LoginRequiredMsg": "The account has logged out, please log in again.",
|
"LoginRequiredMsg": "The account has logged out, please login again.",
|
||||||
"LoginSSHKeySetting": "Login SSH Key",
|
"LoginSSHKeySetting": "Login SSH Key",
|
||||||
"LoginSucceeded": "Login successful",
|
"LoginSucceeded": "Login successful",
|
||||||
"LoginTitleTip": "Note: it will be displayed on the enterprise edition user ssh login koko login page (e.g.: welcome to use jumpserver open source bastion)",
|
"LoginTitleTip": "Note: it will be displayed on the enterprise edition user ssh login koko login page (e.g.: welcome to use jumpserver open source bastion)",
|
||||||
|
@ -666,7 +666,7 @@
|
||||||
"Lowercase": "Lowercase",
|
"Lowercase": "Lowercase",
|
||||||
"LunaSetting": "Luna",
|
"LunaSetting": "Luna",
|
||||||
"MFAErrorMsg": "MFA errors, please check",
|
"MFAErrorMsg": "MFA errors, please check",
|
||||||
"MFAOfUserFirstLoginPersonalInformationImprovementPage": "Enable multi-factor authentication to make your account more secure. <br/>after enabling, you will enter the multi-factor authentication binding process the next time you log in; you can also directly bind in (personal information->quick modification->change multi-factor settings)!",
|
"MFAOfUserFirstLoginPersonalInformationImprovementPage": "Enable multi-factor authentication to make your account more secure. <br/>after enabling, you will enter the multi-factor authentication binding process the next time you login; you can also directly bind in (personal information->quick modification->change multi-factor settings)!",
|
||||||
"MFAOfUserFirstLoginUserGuidePage": "In order to protect your and the company's security, please carefully safeguard important sensitive information such as your account, password, and key (for example, set a complex password, and enable multi-factor authentication) <br/> personal information such as email, mobile number, and wechat are only used for user authentication and platform internal message notifications.",
|
"MFAOfUserFirstLoginUserGuidePage": "In order to protect your and the company's security, please carefully safeguard important sensitive information such as your account, password, and key (for example, set a complex password, and enable multi-factor authentication) <br/> personal information such as email, mobile number, and wechat are only used for user authentication and platform internal message notifications.",
|
||||||
"MailRecipient": "Email recipient",
|
"MailRecipient": "Email recipient",
|
||||||
"MailSend": "Sending",
|
"MailSend": "Sending",
|
||||||
|
@ -863,8 +863,8 @@
|
||||||
"Radius": "Radius",
|
"Radius": "Radius",
|
||||||
"Ranking": "Ranking",
|
"Ranking": "Ranking",
|
||||||
"RazorNotSupport": "Rdp client session, monitoring not supported",
|
"RazorNotSupport": "Rdp client session, monitoring not supported",
|
||||||
"ReLogin": "Log in again",
|
"ReLogin": "Login again",
|
||||||
"ReLoginTitle": "Current third-party login user (cas/saml), not bound to mfa and does not support password verification, please log in again.",
|
"ReLoginTitle": "Current third-party login user (cas/saml), not bound to mfa and does not support password verification, please login again.",
|
||||||
"RealTimeData": "Real-time",
|
"RealTimeData": "Real-time",
|
||||||
"Reason": "Reason",
|
"Reason": "Reason",
|
||||||
"Receivers": "Receiver",
|
"Receivers": "Receiver",
|
||||||
|
@ -916,7 +916,7 @@
|
||||||
"ResetMFAWarningMsg": "Are you sure you want to reset the user's mfa?",
|
"ResetMFAWarningMsg": "Are you sure you want to reset the user's mfa?",
|
||||||
"ResetMFAdSuccessMsg": "Mfa reset successful, user can reset mfa again",
|
"ResetMFAdSuccessMsg": "Mfa reset successful, user can reset mfa again",
|
||||||
"ResetPassword": "Reset password",
|
"ResetPassword": "Reset password",
|
||||||
"ResetPasswordNextLogin": "Password must be changed during next log in",
|
"ResetPasswordNextLogin": "Password must be changed during next login",
|
||||||
"ResetPasswordSuccessMsg": "Reset password message sent to user",
|
"ResetPasswordSuccessMsg": "Reset password message sent to user",
|
||||||
"ResetPasswordWarningMsg": "Are you sure you want to send the password reset email for the user",
|
"ResetPasswordWarningMsg": "Are you sure you want to send the password reset email for the user",
|
||||||
"ResetPublicKeyAndDownload": "Reset and download ssh key",
|
"ResetPublicKeyAndDownload": "Reset and download ssh key",
|
||||||
|
@ -1001,7 +1001,7 @@
|
||||||
"SelectByAttr": "Attribute filter",
|
"SelectByAttr": "Attribute filter",
|
||||||
"SelectFile": "Select file",
|
"SelectFile": "Select file",
|
||||||
"SelectKeyOrCreateNew": "Select tag key or create new one",
|
"SelectKeyOrCreateNew": "Select tag key or create new one",
|
||||||
"SelectLabelFilter": "Select label for search",
|
"SelectLabelFilter": "Select tag for search",
|
||||||
"SelectPlatforms": "Select platform",
|
"SelectPlatforms": "Select platform",
|
||||||
"SelectProperties": "Attributes",
|
"SelectProperties": "Attributes",
|
||||||
"SelectResource": "Select resources",
|
"SelectResource": "Select resources",
|
||||||
|
@ -1235,8 +1235,8 @@
|
||||||
"UserGroupUpdate": "Update the user group",
|
"UserGroupUpdate": "Update the user group",
|
||||||
"UserGroups": "Groups",
|
"UserGroups": "Groups",
|
||||||
"UserList": "Users",
|
"UserList": "Users",
|
||||||
"UserLoginACLHelpMsg": "When logging into the system, the user's login ip and time range can be audited to determine whether they are allowed to log into the system (effective globally)",
|
"UserLoginACLHelpMsg": "When logging into the system, the user's login ip and time range can be audited to determine whether they are allowed to loginto the system (effective globally)",
|
||||||
"UserLoginACLHelpText": "When logging in, it can be audited based on the user's login ip and time segment to determine whether the user can log in",
|
"UserLoginACLHelpText": "When logging in, it can be audited based on the user's login ip and time segment to determine whether the user can login",
|
||||||
"UserLoginAclCreate": "Create user login control",
|
"UserLoginAclCreate": "Create user login control",
|
||||||
"UserLoginAclDetail": "User login control details",
|
"UserLoginAclDetail": "User login control details",
|
||||||
"UserLoginAclList": "User login",
|
"UserLoginAclList": "User login",
|
||||||
|
@ -1296,7 +1296,7 @@
|
||||||
"Zone": "Zone",
|
"Zone": "Zone",
|
||||||
"ZoneCreate": "Create zone",
|
"ZoneCreate": "Create zone",
|
||||||
"ZoneEnabled": "Enable zone",
|
"ZoneEnabled": "Enable zone",
|
||||||
"ZoneHelpMessage": "The zone is the location where assets are located, which can be a data center, public cloud, or VPC. Gateways can be set up within the region. When the network cannot be directly accessed, users can utilize gateways to log in to the assets.",
|
"ZoneHelpMessage": "The zone is the location where assets are located, which can be a data center, public cloud, or VPC. Gateways can be set up within the region. When the network cannot be directly accessed, users can utilize gateways to login to the assets.",
|
||||||
"ZoneList": "Zones",
|
"ZoneList": "Zones",
|
||||||
"ZoneUpdate": "Update the zone",
|
"ZoneUpdate": "Update the zone",
|
||||||
"TailLog": "Tail Log",
|
"TailLog": "Tail Log",
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
# Generated by Django 4.1.13 on 2024-05-09 03:16
|
# Generated by Django 4.1.13 on 2024-05-09 03:16
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
@ -29,7 +30,7 @@ class Migration(migrations.Migration):
|
||||||
('internal', models.BooleanField(default=False, verbose_name='Internal')),
|
('internal', models.BooleanField(default=False, verbose_name='Internal')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'Label',
|
'verbose_name': 'Tag',
|
||||||
'unique_together': {('name', 'value', 'org_id')},
|
'unique_together': {('name', 'value', 'org_id')},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -48,7 +49,7 @@ class Migration(migrations.Migration):
|
||||||
('res_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')),
|
('res_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'Labeled resource',
|
'verbose_name': 'Tagged resource',
|
||||||
'unique_together': {('label', 'res_type', 'res_id', 'org_id')},
|
'unique_together': {('label', 'res_type', 'res_id', 'org_id')},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
@ -13,8 +13,8 @@ class Label(JMSOrgBaseModel):
|
||||||
internal = models.BooleanField(default=False, verbose_name=_("Internal"))
|
internal = models.BooleanField(default=False, verbose_name=_("Internal"))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = [('name', 'value', 'org_id')]
|
unique_together = [("name", "value", "org_id")]
|
||||||
verbose_name = _('Label')
|
verbose_name = _("Tag")
|
||||||
|
|
||||||
@lazyproperty
|
@lazyproperty
|
||||||
def res_count(self):
|
def res_count(self):
|
||||||
|
@ -22,23 +22,28 @@ class Label(JMSOrgBaseModel):
|
||||||
|
|
||||||
@lazyproperty
|
@lazyproperty
|
||||||
def display_name(self):
|
def display_name(self):
|
||||||
return '{}:{}'.format(self.name, self.value)
|
return "{}:{}".format(self.name, self.value)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '{}:{}'.format(self.name, self.value)
|
return "{}:{}".format(self.name, self.value)
|
||||||
|
|
||||||
|
|
||||||
class LabeledResource(JMSOrgBaseModel):
|
class LabeledResource(JMSOrgBaseModel):
|
||||||
label = models.ForeignKey(
|
label = models.ForeignKey(
|
||||||
Label, on_delete=models.CASCADE, related_name='labeled_resources', verbose_name=_("Label")
|
Label,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="labeled_resources",
|
||||||
|
verbose_name=_("Tag"),
|
||||||
)
|
)
|
||||||
res_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
|
res_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
|
||||||
res_id = models.CharField(max_length=36, verbose_name=_("Resource ID"), db_index=True)
|
res_id = models.CharField(
|
||||||
resource = GenericForeignKey('res_type', 'res_id')
|
max_length=36, verbose_name=_("Resource ID"), db_index=True
|
||||||
|
)
|
||||||
|
resource = GenericForeignKey("res_type", "res_id")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = [('label', 'res_type', 'res_id', 'org_id')]
|
unique_together = [("label", "res_type", "res_id", "org_id")]
|
||||||
verbose_name = _('Labeled resource')
|
verbose_name = _("Tagged resource")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '{} => {}'.format(self.label, self.resource)
|
return "{} => {}".format(self.label, self.resource)
|
||||||
|
|
Loading…
Reference in New Issue