From d9aeeb8050c1577bcd1d3b0949ffb90fcd9d65a5 Mon Sep 17 00:00:00 2001 From: Julio-Oliveira-Encora Date: Mon, 6 May 2024 15:45:14 -0300 Subject: [PATCH] Created CloningUserMixin; Added CloningUserMixin to ObjectPermission to enable clone_fields. --- netbox/users/models/features.py | 53 ++++++++++++++++++++++++++++++ netbox/users/models/permissions.py | 5 ++- 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 netbox/users/models/features.py diff --git a/netbox/users/models/features.py b/netbox/users/models/features.py new file mode 100644 index 000000000..b635a08c7 --- /dev/null +++ b/netbox/users/models/features.py @@ -0,0 +1,53 @@ +import json + +from django.db import models + +from extras.utils import is_taggable + + +class CloningUserMixin(models.Model): + """ + Provides the clone() method used to prepare a copy of existing objects. + The same code from netbox/users/models/features.py (CloningMixin) is used here. + It was necessary to avoid circular imports. + """ + class Meta: + abstract = True + + def clone(self): + """ + Returns a dictionary of attributes suitable for creating a copy of the current instance. This is used for pre- + populating an object creation form in the UI. By default, this method will replicate any fields listed in the + model's `clone_fields` list (if defined), but it can be overridden to apply custom logic. + + ```python + class MyModel(NetBoxModel): + def clone(self): + attrs = super().clone() + attrs['extra-value'] = 123 + return attrs + ``` + """ + attrs = {} + + for field_name in getattr(self, 'clone_fields', []): + field = self._meta.get_field(field_name) + field_value = field.value_from_object(self) + if field_value and isinstance(field, models.ManyToManyField): + attrs[field_name] = [v.pk for v in field_value] + elif field_value and isinstance(field, models.JSONField): + attrs[field_name] = json.dumps(field_value) + elif field_value not in (None, ''): + attrs[field_name] = field_value + + # Include tags (if applicable) + if is_taggable(self): + attrs['tags'] = [tag.pk for tag in self.tags.all()] + + # Include any cloneable custom fields + if hasattr(self, 'custom_fields'): + for field in self.custom_fields: + if field.is_cloneable: + attrs[f'cf_{field.name}'] = self.custom_field_data.get(field.name) + + return attrs diff --git a/netbox/users/models/permissions.py b/netbox/users/models/permissions.py index 8b471f12b..662d1a817 100644 --- a/netbox/users/models/permissions.py +++ b/netbox/users/models/permissions.py @@ -4,6 +4,7 @@ from django.urls import reverse from django.utils.translation import gettext_lazy as _ from users.constants import OBJECTPERMISSION_OBJECT_TYPES +from users.models.features import CloningUserMixin from utilities.querysets import RestrictedQuerySet __all__ = ( @@ -11,7 +12,7 @@ __all__ = ( ) -class ObjectPermission(models.Model): +class ObjectPermission(CloningUserMixin, models.Model): """ A mapping of view, add, change, and/or delete permission for users and/or groups to an arbitrary set of objects identified by ORM query parameters. @@ -47,6 +48,8 @@ class ObjectPermission(models.Model): objects = RestrictedQuerySet.as_manager() + clone_fields = ['name', 'description', 'enabled', 'object_types', 'actions', 'constraints'] + class Meta: ordering = ['name'] verbose_name = _('permission')