mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-14 09:51:22 -06:00
Closes #9414: Add clone() method to NetBoxModel for copying instance attributes
This commit is contained in:
parent
12bd3840f9
commit
f9d81fd362
@ -49,6 +49,24 @@ class MyModel(NetBoxModel):
|
||||
...
|
||||
```
|
||||
|
||||
### The `clone()` Method
|
||||
|
||||
!!! info
|
||||
This method was introduced in NetBox v3.3.
|
||||
|
||||
The `NetBoxModel` class includes a `clone()` method to be used for gathering attriubtes which can be used to create a "cloned" instance. This is used primarily for form initialization, e.g. when using the "clone" button in the NetBox UI. By default, this method will replicate any fields listed in the model's `clone_fields` list, if defined.
|
||||
|
||||
Plugin models can leverage this method by defining `clone_fields` as a list of field names to be replicated, or override this method to replace or extend its content:
|
||||
|
||||
```python
|
||||
class MyModel(NetBoxModel):
|
||||
|
||||
def clone(self):
|
||||
attrs = super().clone()
|
||||
attrs['extra-value'] = 123
|
||||
return attrs
|
||||
```
|
||||
|
||||
### Enabling Features Individually
|
||||
|
||||
If you prefer instead to enable only a subset of these features for a plugin model, NetBox provides a discrete "mix-in" class for each feature. You can subclass each of these individually when defining your model. (Your model will also need to inherit from Django's built-in `Model` class.)
|
||||
|
@ -30,6 +30,10 @@
|
||||
* [#9536](https://github.com/netbox-community/netbox/issues/9536) - Track API token usage times
|
||||
* [#9582](https://github.com/netbox-community/netbox/issues/9582) - Enable assigning config contexts based on device location
|
||||
|
||||
### Plugins API
|
||||
|
||||
* [#9414](https://github.com/netbox-community/netbox/issues/9414) - Add `clone()` method to NetBoxModel for copying instance attributes
|
||||
|
||||
### Other Changes
|
||||
|
||||
* [#9261](https://github.com/netbox-community/netbox/issues/9261) - `NetBoxTable` no longer automatically clears pre-existing calls to `prefetch_related()` on its queryset
|
||||
|
@ -2,6 +2,7 @@ from django.core.validators import ValidationError
|
||||
from django.db import models
|
||||
from mptt.models import MPTTModel, TreeForeignKey
|
||||
|
||||
from extras.utils import is_taggable
|
||||
from utilities.mptt import TreeManager
|
||||
from utilities.querysets import RestrictedQuerySet
|
||||
from netbox.models.features import *
|
||||
@ -52,6 +53,25 @@ class NetBoxModel(NetBoxFeatureSet, models.Model):
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def clone(self):
|
||||
"""
|
||||
Return 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.
|
||||
"""
|
||||
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 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()]
|
||||
|
||||
return attrs
|
||||
|
||||
|
||||
class NestedGroupModel(NetBoxFeatureSet, MPTTModel):
|
||||
"""
|
||||
|
@ -394,11 +394,11 @@ class ObjectEditView(GetReturnURLMixin, BaseObjectView):
|
||||
if '_addanother' in request.POST:
|
||||
redirect_url = request.path
|
||||
|
||||
# If the object has clone_fields, pre-populate a new instance of the form
|
||||
# If cloning is supported, pre-populate a new instance of the form
|
||||
params = prepare_cloned_fields(obj)
|
||||
if params:
|
||||
if 'return_url' in request.GET:
|
||||
params['return_url'] = request.GET.get('return_url')
|
||||
if params:
|
||||
redirect_url += f"?{params.urlencode()}"
|
||||
|
||||
return redirect(redirect_url)
|
||||
|
@ -59,7 +59,7 @@ Context:
|
||||
{# Extra buttons #}
|
||||
{% block extra_controls %}{% endblock %}
|
||||
|
||||
{% if object.clone_fields and request.user|can_add:object %}
|
||||
{% if request.user|can_add:object %}
|
||||
{% clone_button object %}
|
||||
{% endif %}
|
||||
{% if request.user|can_change:object %}
|
||||
|
@ -282,26 +282,22 @@ def render_jinja2(template_code, context):
|
||||
|
||||
def prepare_cloned_fields(instance):
|
||||
"""
|
||||
Compile an object's `clone_fields` list into a string of URL query parameters. Tags are automatically cloned where
|
||||
applicable.
|
||||
Generate a QueryDict comprising attributes from an object's clone() method.
|
||||
"""
|
||||
# Generate the clone attributes from the instance
|
||||
if not hasattr(instance, 'clone'):
|
||||
return None
|
||||
attrs = instance.clone()
|
||||
|
||||
# Prepare querydict parameters
|
||||
params = []
|
||||
for field_name in getattr(instance, 'clone_fields', []):
|
||||
field = instance._meta.get_field(field_name)
|
||||
field_value = field.value_from_object(instance)
|
||||
|
||||
# Pass False as null for boolean fields
|
||||
if field_value is False:
|
||||
params.append((field_name, ''))
|
||||
|
||||
# Omit empty values
|
||||
elif field_value not in (None, ''):
|
||||
params.append((field_name, field_value))
|
||||
|
||||
# Copy tags
|
||||
if is_taggable(instance):
|
||||
for tag in instance.tags.all():
|
||||
params.append(('tags', tag.pk))
|
||||
for key, value in attrs.items():
|
||||
if type(value) in (list, tuple):
|
||||
params.extend([(key, v) for v in value])
|
||||
elif value not in (False, None):
|
||||
params.append((key, value))
|
||||
else:
|
||||
params.append((key, ''))
|
||||
|
||||
# Return a QueryDict with the parameters
|
||||
return QueryDict('&'.join([f'{k}={v}' for k, v in params]), mutable=True)
|
||||
|
Loading…
Reference in New Issue
Block a user