diff --git a/README.md b/README.md index 93e125079..654b290ee 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,6 @@ NetBox logo -![Master branch build status](https://github.com/netbox-community/netbox/workflows/CI/badge.svg?branch=master) - NetBox is the leading solution for modeling and documenting modern networks. By combining the traditional disciplines of IP address management (IPAM) and datacenter infrastructure management (DCIM) with powerful APIs and extensions, @@ -11,6 +9,16 @@ NetBox provides the ideal "source of truth" to power network automation. Available as open source software under the Apache 2.0 license, NetBox is employed by thousands of organizations around the world. +![Master branch build status](https://github.com/netbox-community/netbox/workflows/CI/badge.svg?branch=master) + +[![Timeline graph](https://images.repography.com/29023055/netbox-community/netbox/recent-activity/31db894eee74b8a5475e3af307a81b6c_timeline.svg)](https://github.com/netbox-community/netbox/commits) +[![Issue status graph](https://images.repography.com/29023055/netbox-community/netbox/recent-activity/31db894eee74b8a5475e3af307a81b6c_issues.svg)](https://github.com/netbox-community/netbox/issues) +[![Pull request status graph](https://images.repography.com/29023055/netbox-community/netbox/recent-activity/31db894eee74b8a5475e3af307a81b6c_prs.svg)](https://github.com/netbox-community/netbox/pulls) +[![Top contributors](https://images.repography.com/29023055/netbox-community/netbox/recent-activity/31db894eee74b8a5475e3af307a81b6c_users.svg)](https://github.com/netbox-community/netbox/graphs/contributors) +
Stats via [Repography](https://repography.com) + +## About NetBox + ![Screenshot of Netbox UI](docs/media/screenshots/netbox-ui.png "NetBox UI") Myriad infrastructure components can be modeled in NetBox, including: @@ -57,7 +65,7 @@ complete list of requirements, see `requirements.txt`. The code is available [on GitHub](https://github.com/netbox-community/netbox).
-

Thank you to our sponsors!

+

Thank you to our sponsors!

[![DigitalOcean](https://raw.githubusercontent.com/wiki/netbox-community/netbox/images/sponsors/digitalocean.png)](https://try.digitalocean.com/developer-cloud)            diff --git a/docs/installation/3-netbox.md b/docs/installation/3-netbox.md index eeb5e6f20..f42e28deb 100644 --- a/docs/installation/3-netbox.md +++ b/docs/installation/3-netbox.md @@ -7,7 +7,7 @@ This section of the documentation discusses installing and configuring the NetBo Begin by installing all system packages required by NetBox and its dependencies. !!! warning "Python 3.8 or later required" - NetBox v3.2 requires Python 3.8, 3.9, or 3.10. + NetBox requires Python 3.8, 3.9, or 3.10. === "Ubuntu" diff --git a/docs/installation/upgrading.md b/docs/installation/upgrading.md index deeec883a..802c13e49 100644 --- a/docs/installation/upgrading.md +++ b/docs/installation/upgrading.md @@ -1,10 +1,19 @@ # Upgrading to a New NetBox Release -## Review the Release Notes +Upgrading NetBox to a new version is pretty simple, however users are cautioned to always review the release notes and save a backup of their current deployment prior to beginning an upgrade. + +NetBox can generally be upgraded directly to any newer release with no interim steps, with the one exception being incrementing major versions. This can be done only from the most recent _minor_ release of the major version. For example, NetBox v2.11.8 can be upgraded to version 3.3.2 following the steps below. However, a deployment of NetBox v2.10.10 or earlier must first be upgraded to any v2.11 release, and then to any v3.x release. (This is to accommodate the consolidation of database schema migrations effected by a major version change). + +[![Upgrade paths](../media/installation/upgrade_paths.png)](../media/installation/upgrade_paths.png) + +!!! warning "Perform a Backup" + Always be sure to save a backup of your current NetBox deployment prior to starting the upgrade process. + +## 1. Review the Release Notes Prior to upgrading your NetBox instance, be sure to carefully review all [release notes](../release-notes/index.md) that have been published since your current version was released. Although the upgrade process typically does not involve additional work, certain releases may introduce breaking or backward-incompatible changes. These are called out in the release notes under the release in which the change went into effect. -## Update Dependencies to Required Versions +## 2. Update Dependencies to Required Versions NetBox v3.0 and later require the following: @@ -14,7 +23,7 @@ NetBox v3.0 and later require the following: | PostgreSQL | 10 | | Redis | 4.0 | -## Install the Latest Release +## 3. Install the Latest Release 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. @@ -87,7 +96,7 @@ sudo git pull origin master sudo git checkout v2.11.11 -## Run the Upgrade Script +## 4. Run the Upgrade Script Once the new code is in place, verify that any optional Python packages required by your deployment (e.g. `napalm` or `django-auth-ldap`) are listed in `local_requirements.txt`. Then, run the upgrade script: @@ -118,7 +127,7 @@ This script performs the following actions: been made to your local codebase and should be investigated. Never attempt to create new migrations unless you are intentionally modifying the database schema. -## Restart the NetBox Services +## 5. Restart the NetBox Services !!! warning If you are upgrading from an installation that does not use a Python virtual environment (any release prior to v2.7.9), you'll need to update the systemd service files to reference the new Python and gunicorn executables before restarting the services. These are located in `/opt/netbox/venv/bin/`. See the example service files in `/opt/netbox/contrib/` for reference. @@ -129,7 +138,7 @@ Finally, restart the gunicorn and RQ services: sudo systemctl restart netbox netbox-rq ``` -## Verify Housekeeping Scheduling +## 6. Verify Housekeeping Scheduling If upgrading from a release prior to NetBox v3.0, check that a cron task (or similar scheduled process) has been configured to run NetBox's nightly housekeeping command. A shell script which invokes this command is included at `contrib/netbox-housekeeping.sh`. It can be linked from your system's daily cron task directory, or included within the crontab directly. (If NetBox has been installed in a nonstandard path, be sure to update the system paths within this script first.) diff --git a/docs/media/installation/upgrade_paths.png b/docs/media/installation/upgrade_paths.png new file mode 100644 index 000000000..494744b58 Binary files /dev/null and b/docs/media/installation/upgrade_paths.png differ diff --git a/docs/release-notes/version-3.3.md b/docs/release-notes/version-3.3.md index 049e8acc5..480595d56 100644 --- a/docs/release-notes/version-3.3.md +++ b/docs/release-notes/version-3.3.md @@ -9,8 +9,13 @@ ### Bug Fixes * [#9231](https://github.com/netbox-community/netbox/issues/9231) - Fix `empty` lookup expression for string filters +* [#10250](https://github.com/netbox-community/netbox/issues/10250) - Fix exception when CableTermination validation fails during bulk import of cables +* [#10259](https://github.com/netbox-community/netbox/issues/10259) - Fix `NoReverseMatch` exception when listing available prefixes with "flat" column displayed * [#10270](https://github.com/netbox-community/netbox/issues/10270) - Fix custom field validation when creating new services * [#10278](https://github.com/netbox-community/netbox/issues/10278) - Fix "create & add another" for image attachments +* [#10294](https://github.com/netbox-community/netbox/issues/10294) - Fix spurious changelog diff for interface WWN field +* [#10304](https://github.com/netbox-community/netbox/issues/10304) - Enable cloning for custom fields & custom links +* [#10307](https://github.com/netbox-community/netbox/issues/10307) - Correct value for "Passive 48V (4-pair)" PoE type selection --- diff --git a/netbox/dcim/choices.py b/netbox/dcim/choices.py index 019ae09a4..7d35a40f9 100644 --- a/netbox/dcim/choices.py +++ b/netbox/dcim/choices.py @@ -1096,7 +1096,7 @@ class InterfacePoETypeChoices(ChoiceSet): (PASSIVE_24V_2PAIR, 'Passive 24V (2-pair)'), (PASSIVE_24V_4PAIR, 'Passive 24V (4-pair)'), (PASSIVE_48V_2PAIR, 'Passive 48V (2-pair)'), - (PASSIVE_48V_2PAIR, 'Passive 48V (4-pair)'), + (PASSIVE_48V_4PAIR, 'Passive 48V (4-pair)'), ) ), ) diff --git a/netbox/dcim/forms/models.py b/netbox/dcim/forms/models.py index d1d5b1683..a21265db4 100644 --- a/netbox/dcim/forms/models.py +++ b/netbox/dcim/forms/models.py @@ -1331,6 +1331,12 @@ class InterfaceForm(InterfaceCommonForm, NetBoxModelForm): label='VRF' ) + wwn = forms.CharField( + empty_value=None, + required=False, + label='WWN' + ) + fieldsets = ( ('Interface', ('device', 'module', 'name', 'type', 'speed', 'duplex', 'label', 'description', 'tags')), ('Addressing', ('vrf', 'mac_address', 'wwn')), diff --git a/netbox/dcim/models/cables.py b/netbox/dcim/models/cables.py index ab1fe88e4..e05eb6d51 100644 --- a/netbox/dcim/models/cables.py +++ b/netbox/dcim/models/cables.py @@ -281,15 +281,11 @@ class CableTermination(models.Model): # Validate interface type (if applicable) if self.termination_type.model == 'interface' and self.termination.type in NONCONNECTABLE_IFACE_TYPES: - raise ValidationError({ - 'termination': f'Cables cannot be terminated to {self.termination.get_type_display()} interfaces' - }) + raise ValidationError(f"Cables cannot be terminated to {self.termination.get_type_display()} interfaces") # A CircuitTermination attached to a ProviderNetwork cannot have a Cable if self.termination_type.model == 'circuittermination' and self.termination.provider_network is not None: - raise ValidationError({ - 'termination': "Circuit terminations attached to a provider network may not be cabled." - }) + raise ValidationError("Circuit terminations attached to a provider network may not be cabled.") def save(self, *args, **kwargs): diff --git a/netbox/extras/forms/bulk_import.py b/netbox/extras/forms/bulk_import.py index d9148a5c3..e83cac3b9 100644 --- a/netbox/extras/forms/bulk_import.py +++ b/netbox/extras/forms/bulk_import.py @@ -3,7 +3,7 @@ from django.contrib.contenttypes.models import ContentType from django.contrib.postgres.forms import SimpleArrayField from django.utils.safestring import mark_safe -from extras.choices import CustomFieldTypeChoices +from extras.choices import CustomFieldVisibilityChoices, CustomFieldTypeChoices from extras.models import * from extras.utils import FeatureQuery from utilities.forms import CSVChoiceField, CSVContentTypeField, CSVModelForm, CSVMultipleContentTypeField, SlugField @@ -38,6 +38,10 @@ class CustomFieldCSVForm(CSVModelForm): required=False, help_text='Comma-separated list of field choices' ) + ui_visibility = CSVChoiceField( + choices=CustomFieldVisibilityChoices, + help_text='How the custom field is displayed in the user interface' + ) class Meta: model = CustomField diff --git a/netbox/extras/models/customfields.py b/netbox/extras/models/customfields.py index 426565231..43c4f9671 100644 --- a/netbox/extras/models/customfields.py +++ b/netbox/extras/models/customfields.py @@ -14,7 +14,7 @@ from django.utils.safestring import mark_safe from extras.choices import * from extras.utils import FeatureQuery from netbox.models import ChangeLoggedModel -from netbox.models.features import ExportTemplatesMixin, WebhooksMixin +from netbox.models.features import CloningMixin, ExportTemplatesMixin, WebhooksMixin from utilities import filters from utilities.forms import ( CSVChoiceField, CSVMultipleChoiceField, DatePicker, DynamicModelChoiceField, DynamicModelMultipleChoiceField, @@ -41,7 +41,7 @@ class CustomFieldManager(models.Manager.from_queryset(RestrictedQuerySet)): return self.get_queryset().filter(content_types=content_type) -class CustomField(ExportTemplatesMixin, WebhooksMixin, ChangeLoggedModel): +class CustomField(CloningMixin, ExportTemplatesMixin, WebhooksMixin, ChangeLoggedModel): content_types = models.ManyToManyField( to=ContentType, related_name='custom_fields', @@ -143,8 +143,14 @@ class CustomField(ExportTemplatesMixin, WebhooksMixin, ChangeLoggedModel): verbose_name='UI visibility', help_text='Specifies the visibility of custom field in the UI' ) + objects = CustomFieldManager() + clone_fields = ( + 'content_types', 'type', 'object_type', 'group_name', 'description', 'required', 'filter_logic', 'default', + 'weight', 'validation_minimum', 'validation_maximum', 'validation_regex', 'choices', 'ui_visibility', + ) + class Meta: ordering = ['group_name', 'weight', 'name'] diff --git a/netbox/extras/models/models.py b/netbox/extras/models/models.py index 4873a1f9e..0df34c146 100644 --- a/netbox/extras/models/models.py +++ b/netbox/extras/models/models.py @@ -21,7 +21,7 @@ from extras.conditions import ConditionSet from extras.utils import FeatureQuery, image_upload from netbox.models import ChangeLoggedModel from netbox.models.features import ( - CustomFieldsMixin, CustomLinksMixin, ExportTemplatesMixin, JobResultsMixin, TagsMixin, WebhooksMixin, + CloningMixin, CustomFieldsMixin, CustomLinksMixin, ExportTemplatesMixin, JobResultsMixin, TagsMixin, WebhooksMixin, ) from utilities.querysets import RestrictedQuerySet from utilities.utils import render_jinja2 @@ -187,7 +187,7 @@ class Webhook(ExportTemplatesMixin, WebhooksMixin, ChangeLoggedModel): return render_jinja2(self.payload_url, context) -class CustomLink(ExportTemplatesMixin, WebhooksMixin, ChangeLoggedModel): +class CustomLink(CloningMixin, ExportTemplatesMixin, WebhooksMixin, ChangeLoggedModel): """ A custom link to an external representation of a NetBox object. The link text and URL fields accept Jinja2 template code to be rendered with an object as context. @@ -230,6 +230,10 @@ class CustomLink(ExportTemplatesMixin, WebhooksMixin, ChangeLoggedModel): help_text="Force link to open in a new window" ) + clone_fields = ( + 'content_type', 'enabled', 'weight', 'group_name', 'button_class', 'new_window', + ) + class Meta: ordering = ['group_name', 'weight', 'name'] diff --git a/netbox/ipam/tables/ip.py b/netbox/ipam/tables/ip.py index f3c45ec1d..a820385ed 100644 --- a/netbox/ipam/tables/ip.py +++ b/netbox/ipam/tables/ip.py @@ -21,6 +21,14 @@ __all__ = ( AVAILABLE_LABEL = mark_safe('Available') PREFIX_LINK = """ +{% if record.pk %} + {{ record.prefix }} +{% else %} + {{ record.prefix }} +{% endif %} +""" + +PREFIX_LINK_WITH_DEPTH = """ {% load helpers %} {% if record.depth %}
@@ -29,8 +37,7 @@ PREFIX_LINK = """ {% endfor %}
{% endif %} -{{ record.prefix }} -""" +""" + PREFIX_LINK IPADDRESS_LINK = """ {% if record.pk %} @@ -216,14 +223,15 @@ class PrefixUtilizationColumn(columns.UtilizationColumn): class PrefixTable(TenancyColumnsMixin, NetBoxTable): prefix = columns.TemplateColumn( - template_code=PREFIX_LINK, + template_code=PREFIX_LINK_WITH_DEPTH, export_raw=True, attrs={'td': {'class': 'text-nowrap'}} ) - prefix_flat = tables.Column( + prefix_flat = columns.TemplateColumn( accessor=Accessor('prefix'), - linkify=True, - verbose_name='Prefix (Flat)', + template_code=PREFIX_LINK, + export_raw=True, + verbose_name='Prefix (Flat)' ) depth = tables.Column( accessor=Accessor('_depth'), diff --git a/netbox/netbox/models/__init__.py b/netbox/netbox/models/__init__.py index 4c65094ca..aefb733b4 100644 --- a/netbox/netbox/models/__init__.py +++ b/netbox/netbox/models/__init__.py @@ -2,7 +2,6 @@ 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 * @@ -32,7 +31,7 @@ class NetBoxFeatureSet( def get_prerequisite_models(cls): """ Return a list of model types that are required to create this model or empty list if none. This is used for - showing prequisite warnings in the UI on the list and detail views. + showing prerequisite warnings in the UI on the list and detail views. """ return [] @@ -52,7 +51,7 @@ class ChangeLoggedModel(ChangeLoggingMixin, CustomValidationMixin, models.Model) abstract = True -class NetBoxModel(NetBoxFeatureSet, models.Model): +class NetBoxModel(CloningMixin, NetBoxFeatureSet, models.Model): """ Primary models represent real objects within the infrastructure being modeled. """ @@ -61,25 +60,6 @@ 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): """ diff --git a/netbox/netbox/models/features.py b/netbox/netbox/models/features.py index 6b2ee1f94..7f30248b4 100644 --- a/netbox/netbox/models/features.py +++ b/netbox/netbox/models/features.py @@ -10,12 +10,13 @@ from django.db import models from taggit.managers import TaggableManager from extras.choices import CustomFieldVisibilityChoices, ObjectChangeActionChoices -from extras.utils import register_features +from extras.utils import is_taggable, register_features from netbox.signals import post_clean from utilities.utils import serialize_object __all__ = ( 'ChangeLoggingMixin', + 'CloningMixin', 'CustomFieldsMixin', 'CustomLinksMixin', 'CustomValidationMixin', @@ -82,6 +83,33 @@ class ChangeLoggingMixin(models.Model): return objectchange +class CloningMixin(models.Model): + """ + Provides the clone() method used to prepare a copy of existing objects. + """ + 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 CustomFieldsMixin(models.Model): """ Enables support for custom fields. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..6d579b737 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,13 @@ +# See PEP 518 for the spec of this file +# https://www.python.org/dev/peps/pep-0518/ + +[tool.black] +line-length = 120 +target_version = ['py38', 'py39', 'py310'] +skip-string-normalization = true + +[tool.isort] +profile = "black" + +[tool.pylint] +max-line-length = 120 diff --git a/scripts/git-hooks/pre-commit b/scripts/git-hooks/pre-commit index 7a3d680a4..2ccf8df89 100755 --- a/scripts/git-hooks/pre-commit +++ b/scripts/git-hooks/pre-commit @@ -40,10 +40,13 @@ if [ $? != 0 ]; then EXIT=1 fi -echo "Checking UI ESLint, TypeScript, and Prettier compliance..." -yarn --cwd "$PWD/netbox/project-static" validate -if [ $? != 0 ]; then - EXIT=1 +git diff --cached --name-only | if grep --quiet 'netbox/project-static/' +then + echo "Checking UI ESLint, TypeScript, and Prettier compliance..." + yarn --cwd "$PWD/netbox/project-static" validate + if [ $? != 0 ]; then + EXIT=1 + fi fi if [ $EXIT != 0 ]; then