From c991a73632de6c8ebea44977072b0e2f2fe51d15 Mon Sep 17 00:00:00 2001 From: ibuler Date: Sun, 23 Apr 2023 16:15:27 +0800 Subject: [PATCH] v1 --- apps/common/db/fields.py | 104 ++++++++++++++++++++++++++++++++- apps/perms/models/perm_node.py | 9 ++- 2 files changed, 111 insertions(+), 2 deletions(-) diff --git a/apps/common/db/fields.py b/apps/common/db/fields.py index a4a128671..97461774f 100644 --- a/apps/common/db/fields.py +++ b/apps/common/db/fields.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- # -import json from django.core.validators import MinValueValidator, MaxValueValidator from django.db import models @@ -32,6 +31,7 @@ __all__ = [ "PortRangeField", "BitChoices", "TreeChoices", + "JSONManyToManyField", ] @@ -274,3 +274,105 @@ class PortRangeField(models.CharField): kwargs['max_length'] = 16 super().__init__(**kwargs) self.validators.append(PortRangeValidator()) + + +from django.db.models import Q +from django.apps import apps + +from django.db import models +from django.core.exceptions import ValidationError +import json + + +class JSONManyToManyDescriptor: + def __init__(self, field): + self.field = field + self._is_setting = False + + def __get__(self, instance, owner=None): + if instance is None: + return self + + if not hasattr(instance, "_related_manager_cache"): + instance._related_manager_cache = {} + + current_value = getattr(instance, self.field.attname, {}) + + if self.field.name not in instance._related_manager_cache or instance._related_manager_cache[ + self.field.name]._is_value_stale(current_value): + manager = RelatedManager(instance, self.field) + instance._related_manager_cache[self.field.name] = manager + + return instance._related_manager_cache[self.field.name] + + def __set__(self, instance, value): + if instance is None: + return + + if not hasattr(instance, "_is_setting"): + instance._is_setting = {} + + if self.field.name not in instance._is_setting or not instance._is_setting[self.field.name]: + instance._is_setting[self.field.name] = True + manager = self.__get__(instance, instance.__class__) + manager.set(value) + serialized_value = manager.serialize() + instance.__dict__[self.field.attname] = serialized_value + instance._is_setting[self.field.name] = False + + +class JSONManyToManyField(models.JSONField): + def __init__(self, related_model, *args, **kwargs): + self.related_model = related_model + super().__init__(*args, **kwargs) + + def contribute_to_class(self, cls, name, **kwargs): + super().contribute_to_class(cls, name, **kwargs) + setattr(cls, self.name, JSONManyToManyDescriptor(self)) + + def deconstruct(self): + name, path, args, kwargs = super().deconstruct() + kwargs['related_model'] = self.related_model + return name, path, args, kwargs + + def validate(self, value, model_instance): + super().validate(value, model_instance) + if not isinstance(value, list) or not all(isinstance(item, int) for item in value): + raise ValidationError("Invalid JSON data for JSONManyToManyField.") + + +class RelatedManager: + def __init__(self, instance, field): + self.instance = instance + self.field = field + + def _is_value_stale(self, current_value): + return self.serialize() != current_value + + def set(self, value): + self.field.value = value + + def serialize(self): + return self.field.value + + def _get_queryset(self): + model = apps.get_model(self.field.to) + value = self.field.value + + if value["type"] == "all": + return model.objects.all() + elif value["type"] == "ids": + return model.objects.filter(id__in=value["ids"]) + elif value["type"] == "attrs": + filters = Q() + for attr in value["attrs"]: + if attr["match"] == "exact": + filters &= Q(**{attr["attr"]: attr["value"]}) + return model.objects.filter(filters) + + def all(self): + return self._get_queryset() + + def filter(self, *args, **kwargs): + queryset = self._get_queryset() + return queryset.filter(*args, **kwargs) diff --git a/apps/perms/models/perm_node.py b/apps/perms/models/perm_node.py index e51851db9..ccd1898aa 100644 --- a/apps/perms/models/perm_node.py +++ b/apps/perms/models/perm_node.py @@ -2,12 +2,19 @@ from django.db import models from django.db.models import F, TextChoices from django.utils.translation import ugettext_lazy as _ -from assets.models import Asset, Node, FamilyMixin from accounts.models import Account +from assets.models import Asset, Node, FamilyMixin +from common.db.fields import JSONManyToManyField from common.utils import lazyproperty from orgs.mixins.models import JMSOrgBaseModel +class TestPermission2(models.Model): + name = models.CharField(max_length=128, verbose_name=_('Name')) + users = JSONManyToManyField("users.User") + assets = JSONManyToManyField("assets.Asset") + + class NodeFrom(TextChoices): granted = 'granted', 'Direct node granted' child = 'child', 'Have children node'