mirror of
https://github.com/netbox-community/netbox.git
synced 2025-08-24 08:25:17 -06:00
Merge branch 'feature' into 9654-device-weight
This commit is contained in:
commit
4919a14424
@ -4,7 +4,7 @@ bleach
|
||||
|
||||
# The Python web framework on which NetBox is built
|
||||
# https://github.com/django/django
|
||||
Django<4.1
|
||||
Django<4.2
|
||||
|
||||
# Django middleware which permits cross-domain API requests
|
||||
# https://github.com/OttoYiu/django-cors-headers
|
||||
|
@ -25,7 +25,7 @@ ALLOWED_HOSTS = ['*']
|
||||
|
||||
## DATABASE
|
||||
|
||||
NetBox requires access to a PostgreSQL 10 or later database service to store data. This service can run locally on the NetBox server or on a remote system. The following parameters must be defined within the `DATABASE` dictionary:
|
||||
NetBox requires access to a PostgreSQL 11 or later database service to store data. This service can run locally on the NetBox server or on a remote system. The following parameters must be defined within the `DATABASE` dictionary:
|
||||
|
||||
* `NAME` - Database name
|
||||
* `USER` - PostgreSQL username
|
||||
|
@ -58,7 +58,7 @@ Email is sent from NetBox only for critical events or if configured for [logging
|
||||
|
||||
Default: None
|
||||
|
||||
A dictionary of HTTP proxies to use for outbound requests originating from NetBox (e.g. when sending webhook requests). Proxies should be specified by schema (HTTP and HTTPS) as per the [Python requests library documentation](https://2.python-requests.org/en/master/user/advanced/). For example:
|
||||
A dictionary of HTTP proxies to use for outbound requests originating from NetBox (e.g. when sending webhook requests). Proxies should be specified by schema (HTTP and HTTPS) as per the [Python requests library documentation](https://requests.readthedocs.io/en/latest/user/advanced/#proxies). For example:
|
||||
|
||||
```python
|
||||
HTTP_PROXIES = {
|
||||
|
@ -2,8 +2,8 @@
|
||||
|
||||
This section entails the installation and configuration of a local PostgreSQL database. If you already have a PostgreSQL database service in place, skip to [the next section](2-redis.md).
|
||||
|
||||
!!! warning "PostgreSQL 10 or later required"
|
||||
NetBox requires PostgreSQL 10 or later. Please note that MySQL and other relational databases are **not** supported.
|
||||
!!! warning "PostgreSQL 11 or later required"
|
||||
NetBox requires PostgreSQL 11 or later. Please note that MySQL and other relational databases are **not** supported.
|
||||
|
||||
## Installation
|
||||
|
||||
@ -35,7 +35,7 @@ sudo systemctl start postgresql
|
||||
sudo systemctl enable postgresql
|
||||
```
|
||||
|
||||
Before continuing, verify that you have installed PostgreSQL 10 or later:
|
||||
Before continuing, verify that you have installed PostgreSQL 11 or later:
|
||||
|
||||
```no-highlight
|
||||
psql -V
|
||||
|
@ -18,7 +18,7 @@ The following sections detail how to set up a new instance of NetBox:
|
||||
| Dependency | Minimum Version |
|
||||
|------------|-----------------|
|
||||
| Python | 3.8 |
|
||||
| PostgreSQL | 10 |
|
||||
| PostgreSQL | 11 |
|
||||
| Redis | 4.0 |
|
||||
|
||||
Below is a simplified overview of the NetBox application stack for reference:
|
||||
|
@ -20,7 +20,7 @@ NetBox v3.0 and later require the following:
|
||||
| Dependency | Minimum Version |
|
||||
|------------|-----------------|
|
||||
| Python | 3.8 |
|
||||
| PostgreSQL | 10 |
|
||||
| PostgreSQL | 11 |
|
||||
| Redis | 4.0 |
|
||||
|
||||
## 3. Install the Latest Release
|
||||
@ -28,16 +28,15 @@ NetBox v3.0 and later require the following:
|
||||
As with the initial installation, you can upgrade NetBox by either downloading the latest release package or by cloning the `master` branch of the git repository.
|
||||
|
||||
!!! warning
|
||||
Use the same method as you used to install Netbox originally
|
||||
Use the same method as you used to install NetBox originally
|
||||
|
||||
If you are not sure how Netbox was installed originally, check with this
|
||||
command:
|
||||
If you are not sure how NetBox was installed originally, check with this command:
|
||||
|
||||
```
|
||||
ls -ld /opt/netbox /opt/netbox/.git
|
||||
```
|
||||
|
||||
If Netbox was installed from a release package, then `/opt/netbox` will be a
|
||||
If NetBox was installed from a release package, then `/opt/netbox` will be a
|
||||
symlink pointing to the current version, and `/opt/netbox/.git` will not
|
||||
exist. If it was installed from git, then `/opt/netbox` and
|
||||
`/opt/netbox/.git` will both exist as normal directories.
|
||||
|
@ -74,6 +74,6 @@ NetBox is built on the [Django](https://djangoproject.com/) Python framework and
|
||||
| HTTP service | nginx or Apache |
|
||||
| WSGI service | gunicorn or uWSGI |
|
||||
| Application | Django/Python |
|
||||
| Database | PostgreSQL 10+ |
|
||||
| Database | PostgreSQL 11+ |
|
||||
| Task queuing | Redis/django-rq |
|
||||
| Live device access | NAPALM (optional) |
|
||||
|
@ -19,6 +19,10 @@ The wire protocol employed by cooperating servers to maintain the virtual [IP ad
|
||||
|
||||
The group's numeric identifier.
|
||||
|
||||
### Name
|
||||
|
||||
An optional name for the FHRP group.
|
||||
|
||||
### Authentication Type
|
||||
|
||||
The type of authentication employed by group nodes, if any.
|
||||
|
@ -49,24 +49,6 @@ 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 attributes 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.)
|
||||
@ -116,6 +98,8 @@ For more information about database migrations, see the [Django documentation](h
|
||||
|
||||
::: netbox.models.features.ChangeLoggingMixin
|
||||
|
||||
::: netbox.models.features.CloningMixin
|
||||
|
||||
::: netbox.models.features.CustomLinksMixin
|
||||
|
||||
::: netbox.models.features.CustomFieldsMixin
|
||||
|
@ -2,13 +2,17 @@
|
||||
|
||||
## v3.3.5 (FUTURE)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* [#9497](https://github.com/netbox-community/netbox/issues/9497) - Adjust non-racked device filter on site and location detailed view
|
||||
* [#10435](https://github.com/netbox-community/netbox/issues/10435) - Fix exception when filtering VLANs by virtual machine with no cluster assigned
|
||||
* [#10439](https://github.com/netbox-community/netbox/issues/10439) - Fix form widget styling for DeviceType airflow field
|
||||
|
||||
---
|
||||
|
||||
## v3.3.4 (2022-09-16)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* [#9497](https://github.com/netbox-community/netbox/issues/9497) - Adjust non-racked device filter on site and location detailed view
|
||||
* [#10383](https://github.com/netbox-community/netbox/issues/10383) - Fix assignment of component templates to module types via web UI
|
||||
* [#10387](https://github.com/netbox-community/netbox/issues/10387) - Fix `MultiValueDictKeyError` exception when editing a device interface
|
||||
|
||||
|
21
docs/release-notes/version-3.4.md
Normal file
21
docs/release-notes/version-3.4.md
Normal file
@ -0,0 +1,21 @@
|
||||
# NetBox v3.4
|
||||
|
||||
!!! warning "PostgreSQL 11 Required"
|
||||
NetBox v3.4 requires PostgreSQL 11 or later.
|
||||
|
||||
### Enhancements
|
||||
|
||||
* [#9892](https://github.com/netbox-community/netbox/issues/9892) - Add optional `name` field for FHRP groups
|
||||
|
||||
### Plugins API
|
||||
|
||||
* [#10314](https://github.com/netbox-community/netbox/issues/10314) - Move `clone()` method from NetBoxModel to CloningMixin
|
||||
|
||||
### Other Changes
|
||||
|
||||
* [#10358](https://github.com/netbox-community/netbox/issues/10358) - Raise minimum required PostgreSQL version from 10 to 11
|
||||
|
||||
### REST API Changes
|
||||
|
||||
* ipam.FHRPGroup
|
||||
* Added optional `name` field
|
@ -252,6 +252,7 @@ nav:
|
||||
- git Cheat Sheet: 'development/git-cheat-sheet.md'
|
||||
- Release Notes:
|
||||
- Summary: 'release-notes/index.md'
|
||||
- Version 3.4: 'release-notes/version-3.4.md'
|
||||
- Version 3.3: 'release-notes/version-3.3.md'
|
||||
- Version 3.2: 'release-notes/version-3.2.md'
|
||||
- Version 3.1: 'release-notes/version-3.1.md'
|
||||
|
@ -375,6 +375,7 @@ class DeviceTypeForm(NetBoxModelForm):
|
||||
'weight', 'weight_unit', 'front_image', 'rear_image', 'comments', 'tags',
|
||||
]
|
||||
widgets = {
|
||||
'airflow': StaticSelect(),
|
||||
'subdevice_role': StaticSelect(),
|
||||
'front_image': ClearableFileInput(attrs={
|
||||
'accept': DEVICETYPE_IMAGE_FORMATS
|
||||
|
@ -955,12 +955,13 @@ class RearPort(ModularComponentModel, CabledObjectModel):
|
||||
super().clean()
|
||||
|
||||
# Check that positions count is greater than or equal to the number of associated FrontPorts
|
||||
frontport_count = self.frontports.count()
|
||||
if self.positions < frontport_count:
|
||||
raise ValidationError({
|
||||
"positions": f"The number of positions cannot be less than the number of mapped front ports "
|
||||
f"({frontport_count})"
|
||||
})
|
||||
if self.pk:
|
||||
frontport_count = self.frontports.count()
|
||||
if self.positions < frontport_count:
|
||||
raise ValidationError({
|
||||
"positions": f"The number of positions cannot be less than the number of mapped front ports "
|
||||
f"({frontport_count})"
|
||||
})
|
||||
|
||||
|
||||
#
|
||||
|
@ -269,7 +269,7 @@ class DeviceType(NetBoxModel, DeviceWeightMixin):
|
||||
|
||||
if (
|
||||
self.subdevice_role != SubdeviceRoleChoices.ROLE_PARENT
|
||||
) and self.devicebaytemplates.count():
|
||||
) and self.pk and self.devicebaytemplates.count():
|
||||
raise ValidationError({
|
||||
'subdevice_role': "Must delete all device bay templates associated with this device before "
|
||||
"declassifying it as a parent device."
|
||||
|
@ -123,7 +123,7 @@ class FHRPGroupSerializer(NetBoxModelSerializer):
|
||||
class Meta:
|
||||
model = FHRPGroup
|
||||
fields = [
|
||||
'id', 'url', 'display', 'protocol', 'group_id', 'auth_type', 'auth_key', 'description', 'ip_addresses',
|
||||
'id', 'name', 'url', 'display', 'protocol', 'group_id', 'auth_type', 'auth_key', 'description', 'ip_addresses',
|
||||
'tags', 'custom_fields', 'created', 'last_updated',
|
||||
]
|
||||
|
||||
|
@ -653,13 +653,14 @@ class FHRPGroupFilterSet(NetBoxModelFilterSet):
|
||||
|
||||
class Meta:
|
||||
model = FHRPGroup
|
||||
fields = ['id', 'group_id', 'auth_key']
|
||||
fields = ['id', 'group_id', 'name', 'auth_key']
|
||||
|
||||
def search(self, queryset, name, value):
|
||||
if not value.strip():
|
||||
return queryset
|
||||
return queryset.filter(
|
||||
Q(description__icontains=value)
|
||||
Q(description__icontains=value) |
|
||||
Q(name__icontains=value)
|
||||
)
|
||||
|
||||
def filter_related_ip(self, queryset, name, value):
|
||||
|
@ -321,6 +321,10 @@ class FHRPGroupBulkEditForm(NetBoxModelBulkEditForm):
|
||||
required=False,
|
||||
label='Authentication key'
|
||||
)
|
||||
name = forms.CharField(
|
||||
max_length=100,
|
||||
required=False
|
||||
)
|
||||
description = forms.CharField(
|
||||
max_length=200,
|
||||
required=False
|
||||
@ -328,10 +332,10 @@ class FHRPGroupBulkEditForm(NetBoxModelBulkEditForm):
|
||||
|
||||
model = FHRPGroup
|
||||
fieldsets = (
|
||||
(None, ('protocol', 'group_id', 'description')),
|
||||
(None, ('protocol', 'group_id', 'name', 'description')),
|
||||
('Authentication', ('auth_type', 'auth_key')),
|
||||
)
|
||||
nullable_fields = ('auth_type', 'auth_key', 'description')
|
||||
nullable_fields = ('auth_type', 'auth_key', 'name', 'description')
|
||||
|
||||
|
||||
class VLANGroupBulkEditForm(NetBoxModelBulkEditForm):
|
||||
|
@ -326,7 +326,7 @@ class FHRPGroupCSVForm(NetBoxModelCSVForm):
|
||||
|
||||
class Meta:
|
||||
model = FHRPGroup
|
||||
fields = ('protocol', 'group_id', 'auth_type', 'auth_key', 'description')
|
||||
fields = ('protocol', 'group_id', 'auth_type', 'auth_key', 'name', 'description')
|
||||
|
||||
|
||||
class VLANGroupCSVForm(NetBoxModelCSVForm):
|
||||
|
@ -335,9 +335,12 @@ class FHRPGroupFilterForm(NetBoxModelFilterSetForm):
|
||||
model = FHRPGroup
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
('Attributes', ('protocol', 'group_id')),
|
||||
('Attributes', ('name', 'protocol', 'group_id')),
|
||||
('Authentication', ('auth_type', 'auth_key')),
|
||||
)
|
||||
name = forms.CharField(
|
||||
required=False
|
||||
)
|
||||
protocol = MultipleChoiceField(
|
||||
choices=FHRPGroupProtocolChoices,
|
||||
required=False
|
||||
|
@ -527,7 +527,7 @@ class FHRPGroupForm(NetBoxModelForm):
|
||||
)
|
||||
|
||||
fieldsets = (
|
||||
('FHRP Group', ('protocol', 'group_id', 'description', 'tags')),
|
||||
('FHRP Group', ('protocol', 'group_id', 'name', 'description', 'tags')),
|
||||
('Authentication', ('auth_type', 'auth_key')),
|
||||
('Virtual IP Address', ('ip_vrf', 'ip_address', 'ip_status'))
|
||||
)
|
||||
@ -535,7 +535,7 @@ class FHRPGroupForm(NetBoxModelForm):
|
||||
class Meta:
|
||||
model = FHRPGroup
|
||||
fields = (
|
||||
'protocol', 'group_id', 'auth_type', 'auth_key', 'description', 'ip_vrf', 'ip_address', 'ip_status', 'tags',
|
||||
'protocol', 'group_id', 'auth_type', 'auth_key', 'name', 'description', 'ip_vrf', 'ip_address', 'ip_status', 'tags',
|
||||
)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
|
18
netbox/ipam/migrations/0061_fhrpgroup_name.py
Normal file
18
netbox/ipam/migrations/0061_fhrpgroup_name.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.0.7 on 2022-09-20 23:03
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('ipam', '0060_alter_l2vpn_slug'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='fhrpgroup',
|
||||
name='name',
|
||||
field=models.CharField(blank=True, max_length=100),
|
||||
),
|
||||
]
|
@ -22,6 +22,10 @@ class FHRPGroup(NetBoxModel):
|
||||
group_id = models.PositiveSmallIntegerField(
|
||||
verbose_name='Group ID'
|
||||
)
|
||||
name = models.CharField(
|
||||
max_length=100,
|
||||
blank=True
|
||||
)
|
||||
protocol = models.CharField(
|
||||
max_length=50,
|
||||
choices=FHRPGroupProtocolChoices
|
||||
@ -55,7 +59,11 @@ class FHRPGroup(NetBoxModel):
|
||||
verbose_name = 'FHRP group'
|
||||
|
||||
def __str__(self):
|
||||
name = f'{self.get_protocol_display()}: {self.group_id}'
|
||||
name = ''
|
||||
if self.name:
|
||||
name = f'{self.name} '
|
||||
|
||||
name += f'{self.get_protocol_display()}: {self.group_id}'
|
||||
|
||||
# Append the first assigned IP addresses (if any) to serve as an additional identifier
|
||||
if self.pk:
|
||||
|
@ -81,30 +81,34 @@ class VLANQuerySet(RestrictedQuerySet):
|
||||
|
||||
# Find all relevant VLANGroups
|
||||
q = Q()
|
||||
if vm.cluster.site:
|
||||
if vm.cluster.site.region:
|
||||
site = vm.site or vm.cluster.site
|
||||
if vm.cluster:
|
||||
# Add VLANGroups scoped to the assigned cluster (or its group)
|
||||
q |= Q(
|
||||
scope_type=ContentType.objects.get_by_natural_key('virtualization', 'cluster'),
|
||||
scope_id=vm.cluster_id
|
||||
)
|
||||
if vm.cluster.group:
|
||||
q |= Q(
|
||||
scope_type=ContentType.objects.get_by_natural_key('dcim', 'region'),
|
||||
scope_id__in=vm.cluster.site.region.get_ancestors(include_self=True)
|
||||
)
|
||||
if vm.cluster.site.group:
|
||||
q |= Q(
|
||||
scope_type=ContentType.objects.get_by_natural_key('dcim', 'sitegroup'),
|
||||
scope_id__in=vm.cluster.site.group.get_ancestors(include_self=True)
|
||||
scope_type=ContentType.objects.get_by_natural_key('virtualization', 'clustergroup'),
|
||||
scope_id=vm.cluster.group_id
|
||||
)
|
||||
if site:
|
||||
# Add VLANGroups scoped to the assigned site (or its group or region)
|
||||
q |= Q(
|
||||
scope_type=ContentType.objects.get_by_natural_key('dcim', 'site'),
|
||||
scope_id=vm.cluster.site_id
|
||||
scope_id=site.pk
|
||||
)
|
||||
if vm.cluster.group:
|
||||
q |= Q(
|
||||
scope_type=ContentType.objects.get_by_natural_key('virtualization', 'clustergroup'),
|
||||
scope_id=vm.cluster.group_id
|
||||
)
|
||||
q |= Q(
|
||||
scope_type=ContentType.objects.get_by_natural_key('virtualization', 'cluster'),
|
||||
scope_id=vm.cluster_id
|
||||
)
|
||||
if site.region:
|
||||
q |= Q(
|
||||
scope_type=ContentType.objects.get_by_natural_key('dcim', 'region'),
|
||||
scope_id__in=site.region.get_ancestors(include_self=True)
|
||||
)
|
||||
if site.group:
|
||||
q |= Q(
|
||||
scope_type=ContentType.objects.get_by_natural_key('dcim', 'sitegroup'),
|
||||
scope_id__in=site.group.get_ancestors(include_self=True)
|
||||
)
|
||||
vlan_groups = VLANGroup.objects.filter(q)
|
||||
|
||||
# Return all applicable VLANs
|
||||
@ -113,7 +117,7 @@ class VLANQuerySet(RestrictedQuerySet):
|
||||
Q(group__scope_id__isnull=True, site__isnull=True) | # Global group VLANs
|
||||
Q(group__isnull=True, site__isnull=True) # Global VLANs
|
||||
)
|
||||
if vm.cluster.site:
|
||||
q |= Q(site=vm.cluster.site)
|
||||
if site:
|
||||
q |= Q(site=site)
|
||||
|
||||
return self.filter(q)
|
||||
|
@ -36,10 +36,12 @@ class FHRPGroupTable(NetBoxTable):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = FHRPGroup
|
||||
fields = (
|
||||
'pk', 'group_id', 'protocol', 'auth_type', 'auth_key', 'description', 'ip_addresses', 'member_count',
|
||||
'tags', 'created', 'last_updated',
|
||||
'pk', 'group_id', 'protocol', 'name', 'auth_type', 'auth_key', 'description', 'ip_addresses',
|
||||
'member_count', 'tags', 'created', 'last_updated',
|
||||
)
|
||||
default_columns = (
|
||||
'pk', 'group_id', 'protocol', 'name', 'auth_type', 'description', 'ip_addresses', 'member_count',
|
||||
)
|
||||
default_columns = ('pk', 'group_id', 'protocol', 'auth_type', 'description', 'ip_addresses', 'member_count')
|
||||
|
||||
|
||||
class FHRPGroupAssignmentTable(NetBoxTable):
|
||||
|
@ -552,6 +552,7 @@ class FHRPGroupTest(APIViewTestCases.APIViewTestCase):
|
||||
'group_id': 200,
|
||||
'auth_type': FHRPGroupAuthTypeChoices.AUTHENTICATION_MD5,
|
||||
'auth_key': 'foobarbaz999',
|
||||
'name': 'foobar-999',
|
||||
'description': 'New description',
|
||||
}
|
||||
|
||||
|
@ -932,7 +932,7 @@ class FHRPGroupTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
|
||||
fhrp_groups = (
|
||||
FHRPGroup(protocol=FHRPGroupProtocolChoices.PROTOCOL_VRRP2, group_id=10, auth_type=FHRPGroupAuthTypeChoices.AUTHENTICATION_PLAINTEXT, auth_key='foo123'),
|
||||
FHRPGroup(protocol=FHRPGroupProtocolChoices.PROTOCOL_VRRP3, group_id=20, auth_type=FHRPGroupAuthTypeChoices.AUTHENTICATION_MD5, auth_key='bar456'),
|
||||
FHRPGroup(protocol=FHRPGroupProtocolChoices.PROTOCOL_VRRP3, group_id=20, auth_type=FHRPGroupAuthTypeChoices.AUTHENTICATION_MD5, auth_key='bar456', name='bar123'),
|
||||
FHRPGroup(protocol=FHRPGroupProtocolChoices.PROTOCOL_HSRP, group_id=30),
|
||||
)
|
||||
FHRPGroup.objects.bulk_create(fhrp_groups)
|
||||
@ -956,6 +956,10 @@ class FHRPGroupTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
params = {'auth_key': ['foo123', 'bar456']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_name(self):
|
||||
params = {'name': ['bar123', ]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||
|
||||
def test_related_ip(self):
|
||||
# Create some regular IPs to query for related IPs
|
||||
ipaddresses = (
|
||||
|
@ -524,6 +524,7 @@ class FHRPGroupTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
'auth_type': FHRPGroupAuthTypeChoices.AUTHENTICATION_MD5,
|
||||
'auth_key': 'abc123def456',
|
||||
'description': 'Blah blah blah',
|
||||
'name': 'test123 name',
|
||||
'tags': [t.pk for t in tags],
|
||||
}
|
||||
|
||||
|
@ -92,8 +92,17 @@ class CloningMixin(models.Model):
|
||||
|
||||
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.
|
||||
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 = {}
|
||||
|
||||
|
@ -26,6 +26,10 @@
|
||||
<td>Group ID</td>
|
||||
<td>{{ object.group_id }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
<td>{{ object.name|placeholder }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Description</td>
|
||||
<td>{{ object.description|placeholder }}</td>
|
||||
|
@ -8,6 +8,7 @@
|
||||
</div>
|
||||
{% render_field form.protocol %}
|
||||
{% render_field form.group_id %}
|
||||
{% render_field form.name %}
|
||||
{% render_field form.description %}
|
||||
{% render_field form.tags %}
|
||||
</div>
|
||||
|
@ -367,7 +367,7 @@ class VirtualMachine(NetBoxModel, ConfigContextModel):
|
||||
})
|
||||
|
||||
# Validate primary IP addresses
|
||||
interfaces = self.interfaces.all()
|
||||
interfaces = self.interfaces.all() if self.pk else None
|
||||
for family in (4, 6):
|
||||
field = f'primary_ip{family}'
|
||||
ip = getattr(self, field)
|
||||
|
@ -1,5 +1,5 @@
|
||||
bleach==5.0.1
|
||||
Django==4.0.7
|
||||
Django==4.1.1
|
||||
django-cors-headers==3.13.0
|
||||
django-debug-toolbar==3.6.0
|
||||
django-filter==22.1
|
||||
|
Loading…
Reference in New Issue
Block a user