diff --git a/apps/common/db/fields.py b/apps/common/db/fields.py index 22640fb5d..00a77826a 100644 --- a/apps/common/db/fields.py +++ b/apps/common/db/fields.py @@ -2,10 +2,10 @@ # import json +from django.core.validators import MinValueValidator, MaxValueValidator from django.db import models -from django.utils.translation import ugettext_lazy as _ from django.utils.encoding import force_text -from django.core.validators import MinValueValidator, MaxValueValidator +from django.utils.translation import ugettext_lazy as _ from common.utils import signer, crypto @@ -211,22 +211,28 @@ class BitChoices(models.IntegerChoices): def branches(cls): return [i for i in cls] + @classmethod + def is_tree(cls): + return False + @classmethod def tree(cls): + if not cls.is_tree(): + return [] root = [_("All"), cls.branches()] - return cls.render_node(root) + return [cls.render_node(root)] @classmethod def render_node(cls, node): if isinstance(node, BitChoices): return { - "id": node.name, + "value": node.name, "label": node.label, } else: name, children = node return { - "id": name, + "value": name, "label": name, "children": [cls.render_node(child) for child in children], } diff --git a/apps/common/drf/fields.py b/apps/common/drf/fields.py index 1e68265ab..9f2527d8a 100644 --- a/apps/common/drf/fields.py +++ b/apps/common/drf/fields.py @@ -2,11 +2,11 @@ # import six from django.core.exceptions import ObjectDoesNotExist -from django.db.models import IntegerChoices from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from rest_framework.fields import ChoiceField +from common.db.fields import BitChoices from common.utils import decrypt_password __all__ = [ @@ -15,6 +15,7 @@ __all__ = [ "LabeledChoiceField", "ObjectRelatedField", "BitChoicesField", + "TreeChoicesMixin" ] @@ -102,14 +103,19 @@ class ObjectRelatedField(serializers.RelatedField): self.fail("incorrect_type", data_type=type(pk).__name__) -class BitChoicesField(serializers.MultipleChoiceField): +class TreeChoicesMixin: + tree = [] + + +class BitChoicesField(TreeChoicesMixin, serializers.MultipleChoiceField): """ 位字段 """ def __init__(self, choice_cls, **kwargs): - assert issubclass(choice_cls, IntegerChoices) + assert issubclass(choice_cls, BitChoices) choices = [(c.name, c.label) for c in choice_cls] + self.tree = choice_cls.tree() self._choice_cls = choice_cls super().__init__(choices=choices, **kwargs) diff --git a/apps/common/drf/metadata.py b/apps/common/drf/metadata.py index d16ab2262..fc9ceb961 100644 --- a/apps/common/drf/metadata.py +++ b/apps/common/drf/metadata.py @@ -13,6 +13,8 @@ from rest_framework.fields import empty from rest_framework.metadata import SimpleMetadata from rest_framework.request import clone_request +from common.drf.fields import TreeChoicesMixin + class SimpleMetadataWithFilters(SimpleMetadata): """Override SimpleMetadata, adding info about filters""" @@ -59,13 +61,45 @@ class SimpleMetadataWithFilters(SimpleMetadata): view.request = request return actions + def get_field_type(self, field): + """ + Given a field, return a string representing the type of the field. + """ + tp = self.label_lookup[field] + + class_name = field.__class__.__name__ + if class_name == "LabeledChoiceField": + tp = "labeled_choice" + elif class_name == "ObjectRelatedField": + tp = "object_related_field" + elif class_name == "ManyRelatedField": + child_relation_class_name = field.child_relation.__class__.__name__ + if child_relation_class_name == "ObjectRelatedField": + tp = "m2m_related_field" + return tp + + @staticmethod + def set_choices_field(field, field_info): + field_info["choices"] = [ + { + "value": choice_value, + "label": force_text(choice_label, strings_only=True), + } + for choice_value, choice_label in dict(field.choices).items() + ] + + @staticmethod + def set_tree_field(field, field_info): + field_info["tree"] = field.tree + field_info["type"] = "tree" + def get_field_info(self, field): """ Given an instance of a serializer field, return a dictionary of metadata about it. """ field_info = OrderedDict() - field_info["type"] = self.label_lookup[field] + field_info["type"] = self.get_field_type(field) field_info["required"] = getattr(field, "required", False) # Default value @@ -84,25 +118,10 @@ class SimpleMetadataWithFilters(SimpleMetadata): elif getattr(field, "fields", None): field_info["children"] = self.get_serializer_info(field) - is_choice_field = isinstance(field, (serializers.ChoiceField,)) - if is_choice_field and hasattr(field, "choices"): - field_info["choices"] = [ - { - "value": choice_value, - "label": force_text(choice_label, strings_only=True), - } - for choice_value, choice_label in dict(field.choices).items() - ] - - class_name = field.__class__.__name__ - if class_name == "LabeledChoiceField": - field_info["type"] = "labeled_choice" - elif class_name == "ObjectRelatedField": - field_info["type"] = "object_related_field" - elif class_name == "ManyRelatedField": - child_relation_class_name = field.child_relation.__class__.__name__ - if child_relation_class_name == "ObjectRelatedField": - field_info["type"] = "m2m_related_field" + if isinstance(field, TreeChoicesMixin): + self.set_tree_field(field, field_info) + elif isinstance(field, serializers.ChoiceField): + self.set_choices_field(field, field_info) return field_info @staticmethod @@ -130,7 +149,8 @@ class SimpleMetadataWithFilters(SimpleMetadata): fields = list(fields.keys()) return fields - def get_ordering_fields(self, request, view): + @staticmethod + def get_ordering_fields(request, view): fields = [] if hasattr(view, "get_ordering_fields"): fields = view.get_ordering_fields(request) diff --git a/apps/perms/const.py b/apps/perms/const.py index 3dd7aad6a..b78d6b48b 100644 --- a/apps/perms/const.py +++ b/apps/perms/const.py @@ -3,69 +3,30 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ -from common.utils.integer import bit from common.db.fields import BitChoices +from common.utils.integer import bit - -__all__ = ['SpecialAccount', 'ActionChoices'] +__all__ = ["SpecialAccount", "ActionChoices"] class ActionChoices(BitChoices): - connect = bit(0), _('Connect') - upload = bit(1), _('Upload') - download = bit(2), _('Download') - copy = bit(3), _('Copy') - paste = bit(4), _('Paste') + connect = bit(0), _("Connect") + upload = bit(1), _("Upload") + download = bit(2), _("Download") + copy = bit(3), _("Copy") + paste = bit(4), _("Paste") + + @classmethod + def is_tree(cls): + return True @classmethod def branches(cls): return ( - (_('Transfer'), [cls.upload, cls.download]), - (_('Clipboard'), [cls.copy, cls.paste]), + (_("Transfer"), [cls.upload, cls.download]), + (_("Clipboard"), [cls.copy, cls.paste]), ) -# class Action(BitOperationChoice): -# CONNECT = 0b1 -# UPLOAD = 0b1 << 1 -# DOWNLOAD = 0b1 << 2 -# COPY = 0b1 << 3 -# PASTE = 0b1 << 4 -# ALL = 0 << 8 -# TRANSFER = UPLOAD | DOWNLOAD -# CLIPBOARD = COPY | PASTE -# -# DB_CHOICES = ( -# (ALL, _('All')), -# (CONNECT, _('Connect')), -# (UPLOAD, _('Upload file')), -# (DOWNLOAD, _('Download file')), -# (TRANSFER, _("Upload download")), -# (COPY, _('Clipboard copy')), -# (PASTE, _('Clipboard paste')), -# (CLIPBOARD, _('Clipboard copy paste')) -# ) -# -# NAME_MAP = { -# ALL: "all", -# CONNECT: "connect", -# UPLOAD: "upload", -# DOWNLOAD: "download", -# TRANSFER: "transfer", -# COPY: 'copy', -# PASTE: 'paste', -# CLIPBOARD: 'clipboard' -# } -# -# NAME_MAP_REVERSE = {v: k for k, v in NAME_MAP.items()} -# CHOICES = [] -# for i, j in DB_CHOICES: -# CHOICES.append((NAME_MAP[i], j)) -# -# @classmethod -# def choices(cls): -# pass -# - class SpecialAccount(models.TextChoices): - ALL = '@ALL', 'All' + ALL = "@ALL", "All"