From 7ac6dff96d79f37467a764f996e4f990c5d524ae Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 11 Oct 2024 07:43:46 -0400 Subject: [PATCH] Closes #17733: Replace `pycodestyle` with `ruff` (#17734) * Resolve F541 errors * Resolve F841 errors * Resolve F811 errors * Resolve F901 errors * Resolve E714 errors * Ignore F821 errors for GraphQL mixins * Replace pycodestyle with ruff * Move ignores to ruff.toml --- .github/workflows/ci.yml | 4 +- docs/development/getting-started.md | 4 +- docs/development/style-guide.md | 36 ++++++------- netbox/circuits/api/nested_serializers.py | 2 +- netbox/circuits/tests/test_views.py | 2 +- netbox/core/api/nested_serializers.py | 2 +- netbox/core/data_backends.py | 2 +- netbox/core/graphql/mixins.py | 2 +- .../management/commands/syncdatasource.py | 4 +- netbox/core/models/data.py | 2 +- netbox/core/models/jobs.py | 4 +- netbox/dcim/api/nested_serializers.py | 2 +- netbox/dcim/forms/object_create.py | 4 +- netbox/dcim/graphql/mixins.py | 40 +++++++------- .../dcim/management/commands/trace_paths.py | 2 +- .../dcim/models/device_component_templates.py | 1 - netbox/dcim/tables/devicetypes.py | 3 +- netbox/dcim/tests/test_api.py | 4 +- netbox/dcim/tests/test_filtersets.py | 7 --- netbox/dcim/tests/test_models.py | 54 +++++++++---------- netbox/dcim/tests/test_views.py | 2 +- netbox/extras/api/nested_serializers.py | 2 +- netbox/extras/jobs.py | 2 +- .../management/commands/housekeeping.py | 4 +- netbox/extras/management/commands/reindex.py | 4 +- .../extras/management/commands/runscript.py | 2 +- netbox/extras/models/customfields.py | 2 +- netbox/extras/scripts.py | 2 +- netbox/extras/tests/test_customvalidators.py | 2 +- netbox/extras/tests/test_models.py | 8 +-- netbox/extras/tests/test_views.py | 2 +- netbox/ipam/api/nested_serializers.py | 2 +- netbox/ipam/api/views.py | 6 +-- netbox/ipam/graphql/mixins.py | 4 +- netbox/ipam/tests/test_api.py | 2 - netbox/ipam/tests/test_views.py | 8 +-- netbox/netbox/config/__init__.py | 2 +- netbox/netbox/data_backends.py | 2 +- netbox/netbox/navigation/menu.py | 32 +++++------ netbox/netbox/settings.py | 2 +- netbox/netbox/staging.py | 2 +- netbox/netbox/tests/dummy_plugin/views.py | 4 +- netbox/netbox/tests/test_authentication.py | 2 +- netbox/netbox/tests/test_import.py | 1 - netbox/tenancy/api/nested_serializers.py | 2 +- netbox/tenancy/graphql/mixins.py | 2 +- netbox/users/api/nested_serializers.py | 2 +- netbox/users/api/views.py | 2 +- netbox/users/forms/model_forms.py | 1 - netbox/users/tests/test_preferences.py | 4 +- netbox/utilities/forms/fields/dynamic.py | 2 +- netbox/utilities/html.py | 2 +- netbox/utilities/tests/test_api.py | 4 +- netbox/utilities/tests/test_counters.py | 2 +- .../virtualization/api/nested_serializers.py | 2 +- netbox/virtualization/tests/test_views.py | 14 ++--- netbox/virtualization/views.py | 4 +- netbox/vpn/api/nested_serializers.py | 2 +- netbox/vpn/filtersets.py | 11 ---- netbox/vpn/tests/test_filtersets.py | 7 --- netbox/vpn/tests/test_views.py | 8 +-- netbox/wireless/api/nested_serializers.py | 2 +- netbox/wireless/tests/test_views.py | 6 +-- ruff.toml | 2 + scripts/git-hooks/pre-commit | 4 +- 65 files changed, 168 insertions(+), 199 deletions(-) create mode 100644 ruff.toml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b4be03742..c3b4876c3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -73,7 +73,7 @@ jobs: run: | python -m pip install --upgrade pip pip install -r requirements.txt - pip install pycodestyle coverage tblib + pip install ruff coverage tblib - name: Build documentation run: mkdocs build @@ -85,7 +85,7 @@ jobs: run: python netbox/manage.py makemigrations --check - name: Check PEP8 compliance - run: pycodestyle --ignore=W504,E501 --exclude=node_modules netbox/ + run: ruff check netbox/ - name: Check UI ESLint, TypeScript, and Prettier Compliance run: yarn --cwd netbox/project-static validate diff --git a/docs/development/getting-started.md b/docs/development/getting-started.md index 4dbdb63b2..374ed34b0 100644 --- a/docs/development/getting-started.md +++ b/docs/development/getting-started.md @@ -70,10 +70,10 @@ NetBox ships with a [git pre-commit hook](https://githooks.com/) script that aut cd .git/hooks/ ln -s ../../scripts/git-hooks/pre-commit ``` -For the pre-commit hooks to work, you will also need to install the pycodestyle package: +For the pre-commit hooks to work, you will also need to install the [ruff](https://docs.astral.sh/ruff/) linter: ```no-highlight -python -m pip install pycodestyle +python -m pip install ruff ``` ...and set up the yarn packages as shown in the [Web UI Development Guide](web-ui.md) diff --git a/docs/development/style-guide.md b/docs/development/style-guide.md index 9e5606749..0d4caf395 100644 --- a/docs/development/style-guide.md +++ b/docs/development/style-guide.md @@ -1,6 +1,6 @@ # Style Guide -NetBox generally follows the [Django style guide](https://docs.djangoproject.com/en/stable/internals/contributing/writing-code/coding-style/), which is itself based on [PEP 8](https://www.python.org/dev/peps/pep-0008/). [Pycodestyle](https://github.com/pycqa/pycodestyle) is used to validate code formatting, ignoring certain violations. +NetBox generally follows the [Django style guide](https://docs.djangoproject.com/en/stable/internals/contributing/writing-code/coding-style/), which is itself based on [PEP 8](https://www.python.org/dev/peps/pep-0008/). [ruff](https://docs.astral.sh/ruff/) is used for linting (with certain [exceptions](#linter-exceptions)). ## Code @@ -20,32 +20,32 @@ NetBox generally follows the [Django style guide](https://docs.djangoproject.com * Nested API serializers generate minimal representations of an object. These are stored separately from the primary serializers to avoid circular dependencies. Always import nested serializers from other apps directly. For example, from within the DCIM app you would write `from ipam.api.nested_serializers import NestedIPAddressSerializer`. -### PEP 8 Exceptions +### Linting -NetBox ignores certain PEP8 assertions. These are listed below. +The [ruff](https://docs.astral.sh/ruff/) linter is used to enforce code style. A [pre-commit hook](./getting-started.md#3-enable-pre-commit-hooks) which runs this automatically is included with NetBox. To invoke `ruff` manually, run: -#### Wildcard Imports +``` +ruff check netbox/ +``` + +#### Linter Exceptions + +The following rules are ignored when linting. + +##### [E501](https://docs.astral.sh/ruff/rules/line-too-long/): Line too long + +NetBox does not enforce a hard restriction on line length, although a maximum length of 120 characters is strongly encouraged for Python code where possible. The maximum length does not apply to HTML templates or to automatically generated code (e.g. database migrations). + +##### [F403](https://docs.astral.sh/ruff/rules/undefined-local-with-import-star/): Undefined local with import star Wildcard imports (for example, `from .constants import *`) are acceptable under any of the following conditions: * The library being import contains only constant declarations (e.g. `constants.py`) * The library being imported explicitly defines `__all__` -#### Maximum Line Length (E501) +##### [F405](https://docs.astral.sh/ruff/rules/undefined-local-with-import-star-usage/): Undefined local with import star usage -NetBox does not restrict lines to a maximum length of 79 characters. We use a maximum line length of 120 characters, however this is not enforced by CI. The maximum length does not apply to HTML templates or to automatically generated code (e.g. database migrations). - -#### Line Breaks Following Binary Operators (W504) - -Line breaks are permitted following binary operators. - -### Enforcing Code Style - -The [`pycodestyle`](https://pypi.org/project/pycodestyle/) utility (formerly `pep8`) is used by the CI process to enforce code style. A [pre-commit hook](./getting-started.md#3-enable-pre-commit-hooks) which runs this automatically is included with NetBox. To invoke `pycodestyle` manually, run: - -``` -pycodestyle --ignore=W504,E501 netbox/ -``` +The justification for ignoring this rule is the same as F403 above. ### Introducing New Dependencies diff --git a/netbox/circuits/api/nested_serializers.py b/netbox/circuits/api/nested_serializers.py index 6de2cbf54..487749872 100644 --- a/netbox/circuits/api/nested_serializers.py +++ b/netbox/circuits/api/nested_serializers.py @@ -18,7 +18,7 @@ __all__ = [ # TODO: Remove in v4.2 warnings.warn( - f"Dedicated nested serializers will be removed in NetBox v4.2. Use Serializer(nested=True) instead.", + "Dedicated nested serializers will be removed in NetBox v4.2. Use Serializer(nested=True) instead.", DeprecationWarning ) diff --git a/netbox/circuits/tests/test_views.py b/netbox/circuits/tests/test_views.py index 87e6d99b7..b06ade30b 100644 --- a/netbox/circuits/tests/test_views.py +++ b/netbox/circuits/tests/test_views.py @@ -171,7 +171,7 @@ class CircuitTestCase(ViewTestCases.PrimaryObjectViewTestCase): ) cls.csv_update_data = ( - f"id,cid,description,status", + "id,cid,description,status", f"{circuits[0].pk},Circuit 7,New description7,{CircuitStatusChoices.STATUS_DECOMMISSIONED}", f"{circuits[1].pk},Circuit 8,New description8,{CircuitStatusChoices.STATUS_DECOMMISSIONED}", f"{circuits[2].pk},Circuit 9,New description9,{CircuitStatusChoices.STATUS_DECOMMISSIONED}", diff --git a/netbox/core/api/nested_serializers.py b/netbox/core/api/nested_serializers.py index 3b40853cf..df7b41ca7 100644 --- a/netbox/core/api/nested_serializers.py +++ b/netbox/core/api/nested_serializers.py @@ -16,7 +16,7 @@ __all__ = ( # TODO: Remove in v4.2 warnings.warn( - f"Dedicated nested serializers will be removed in NetBox v4.2. Use Serializer(nested=True) instead.", + "Dedicated nested serializers will be removed in NetBox v4.2. Use Serializer(nested=True) instead.", DeprecationWarning ) diff --git a/netbox/core/data_backends.py b/netbox/core/data_backends.py index 8b36c6995..770a3b258 100644 --- a/netbox/core/data_backends.py +++ b/netbox/core/data_backends.py @@ -34,7 +34,7 @@ class LocalBackend(DataBackend): @contextmanager def fetch(self): - logger.debug(f"Data source type is local; skipping fetch") + logger.debug("Data source type is local; skipping fetch") local_path = urlparse(self.url).path # Strip file:// scheme yield local_path diff --git a/netbox/core/graphql/mixins.py b/netbox/core/graphql/mixins.py index 43f8761d1..5195b52a0 100644 --- a/netbox/core/graphql/mixins.py +++ b/netbox/core/graphql/mixins.py @@ -15,7 +15,7 @@ __all__ = ( class ChangelogMixin: @strawberry_django.field - def changelog(self, info) -> List[Annotated["ObjectChangeType", strawberry.lazy('.types')]]: + def changelog(self, info) -> List[Annotated["ObjectChangeType", strawberry.lazy('.types')]]: # noqa: F821 content_type = ContentType.objects.get_for_model(self) object_changes = ObjectChange.objects.filter( changed_object_type=content_type, diff --git a/netbox/core/management/commands/syncdatasource.py b/netbox/core/management/commands/syncdatasource.py index aa8137952..990b6eb2a 100644 --- a/netbox/core/management/commands/syncdatasource.py +++ b/netbox/core/management/commands/syncdatasource.py @@ -26,7 +26,7 @@ class Command(BaseCommand): if invalid_names := set(options['name']) - found_names: raise CommandError(f"Invalid data source names: {', '.join(invalid_names)}") else: - raise CommandError(f"Must specify at least one data source, or set --all.") + raise CommandError("Must specify at least one data source, or set --all.") if len(options['name']) > 1: self.stdout.write(f"Syncing {len(datasources)} data sources.") @@ -43,4 +43,4 @@ class Command(BaseCommand): raise e if len(options['name']) > 1: - self.stdout.write(f"Finished.") + self.stdout.write("Finished.") diff --git a/netbox/core/models/data.py b/netbox/core/models/data.py index 717449506..6824b4605 100644 --- a/netbox/core/models/data.py +++ b/netbox/core/models/data.py @@ -125,7 +125,7 @@ class DataSource(JobsMixin, PrimaryModel): # Ensure URL scheme matches selected type if self.backend_class.is_local and self.url_scheme not in ('file', ''): raise ValidationError({ - 'source_url': f"URLs for local sources must start with file:// (or specify no scheme)" + 'source_url': "URLs for local sources must start with file:// (or specify no scheme)" }) def to_objectchange(self, action): diff --git a/netbox/core/models/jobs.py b/netbox/core/models/jobs.py index 655647ebb..e1b5715dd 100644 --- a/netbox/core/models/jobs.py +++ b/netbox/core/models/jobs.py @@ -118,9 +118,9 @@ class Job(models.Model): # TODO: Employ dynamic registration if self.object_type: if self.object_type.model == 'reportmodule': - return reverse(f'extras:report_result', kwargs={'job_pk': self.pk}) + return reverse('extras:report_result', kwargs={'job_pk': self.pk}) elif self.object_type.model == 'scriptmodule': - return reverse(f'extras:script_result', kwargs={'job_pk': self.pk}) + return reverse('extras:script_result', kwargs={'job_pk': self.pk}) return reverse('core:job', args=[self.pk]) def get_status_color(self): diff --git a/netbox/dcim/api/nested_serializers.py b/netbox/dcim/api/nested_serializers.py index 5d83b9145..4b8f0db4a 100644 --- a/netbox/dcim/api/nested_serializers.py +++ b/netbox/dcim/api/nested_serializers.py @@ -56,7 +56,7 @@ __all__ = [ # TODO: Remove in v4.2 warnings.warn( - f"Dedicated nested serializers will be removed in NetBox v4.2. Use Serializer(nested=True) instead.", + "Dedicated nested serializers will be removed in NetBox v4.2. Use Serializer(nested=True) instead.", DeprecationWarning ) diff --git a/netbox/dcim/forms/object_create.py b/netbox/dcim/forms/object_create.py index f811700b4..d18c7ed14 100644 --- a/netbox/dcim/forms/object_create.py +++ b/netbox/dcim/forms/object_create.py @@ -261,8 +261,8 @@ class FrontPortCreateForm(ComponentCreateForm, model_forms.FrontPortForm): # TODO: Clean up the application of HTMXSelect attributes attrs={ 'hx-get': '.', - 'hx-include': f'#form_fields', - 'hx-target': f'#form_fields', + 'hx-include': '#form_fields', + 'hx-target': '#form_fields', } ) ) diff --git a/netbox/dcim/graphql/mixins.py b/netbox/dcim/graphql/mixins.py index a489ef1f6..2e5ab7ea7 100644 --- a/netbox/dcim/graphql/mixins.py +++ b/netbox/dcim/graphql/mixins.py @@ -10,18 +10,18 @@ __all__ = ( @strawberry.type class CabledObjectMixin: - cable: Annotated["CableType", strawberry.lazy('dcim.graphql.types')] | None + cable: Annotated["CableType", strawberry.lazy('dcim.graphql.types')] | None # noqa: F821 link_peers: List[Annotated[Union[ - Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')], - Annotated["ConsolePortType", strawberry.lazy('dcim.graphql.types')], - Annotated["ConsoleServerPortType", strawberry.lazy('dcim.graphql.types')], - Annotated["FrontPortType", strawberry.lazy('dcim.graphql.types')], - Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')], - Annotated["PowerFeedType", strawberry.lazy('dcim.graphql.types')], - Annotated["PowerOutletType", strawberry.lazy('dcim.graphql.types')], - Annotated["PowerPortType", strawberry.lazy('dcim.graphql.types')], - Annotated["RearPortType", strawberry.lazy('dcim.graphql.types')], + Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')], # noqa: F821 + Annotated["ConsolePortType", strawberry.lazy('dcim.graphql.types')], # noqa: F821 + Annotated["ConsoleServerPortType", strawberry.lazy('dcim.graphql.types')], # noqa: F821 + Annotated["FrontPortType", strawberry.lazy('dcim.graphql.types')], # noqa: F821 + Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')], # noqa: F821 + Annotated["PowerFeedType", strawberry.lazy('dcim.graphql.types')], # noqa: F821 + Annotated["PowerOutletType", strawberry.lazy('dcim.graphql.types')], # noqa: F821 + Annotated["PowerPortType", strawberry.lazy('dcim.graphql.types')], # noqa: F821 + Annotated["RearPortType", strawberry.lazy('dcim.graphql.types')], # noqa: F821 ], strawberry.union("LinkPeerType")]] @@ -29,14 +29,14 @@ class CabledObjectMixin: class PathEndpointMixin: connected_endpoints: List[Annotated[Union[ - Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')], - Annotated["ConsolePortType", strawberry.lazy('dcim.graphql.types')], - Annotated["ConsoleServerPortType", strawberry.lazy('dcim.graphql.types')], - Annotated["FrontPortType", strawberry.lazy('dcim.graphql.types')], - Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')], - Annotated["PowerFeedType", strawberry.lazy('dcim.graphql.types')], - Annotated["PowerOutletType", strawberry.lazy('dcim.graphql.types')], - Annotated["PowerPortType", strawberry.lazy('dcim.graphql.types')], - Annotated["ProviderNetworkType", strawberry.lazy('circuits.graphql.types')], - Annotated["RearPortType", strawberry.lazy('dcim.graphql.types')], + Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')], # noqa: F821 + Annotated["ConsolePortType", strawberry.lazy('dcim.graphql.types')], # noqa: F821 + Annotated["ConsoleServerPortType", strawberry.lazy('dcim.graphql.types')], # noqa: F821 + Annotated["FrontPortType", strawberry.lazy('dcim.graphql.types')], # noqa: F821 + Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')], # noqa: F821 + Annotated["PowerFeedType", strawberry.lazy('dcim.graphql.types')], # noqa: F821 + Annotated["PowerOutletType", strawberry.lazy('dcim.graphql.types')], # noqa: F821 + Annotated["PowerPortType", strawberry.lazy('dcim.graphql.types')], # noqa: F821 + Annotated["ProviderNetworkType", strawberry.lazy('circuits.graphql.types')], # noqa: F821 + Annotated["RearPortType", strawberry.lazy('dcim.graphql.types')], # noqa: F821 ], strawberry.union("ConnectedEndpointType")]] diff --git a/netbox/dcim/management/commands/trace_paths.py b/netbox/dcim/management/commands/trace_paths.py index d34a428e4..592aeb6a7 100644 --- a/netbox/dcim/management/commands/trace_paths.py +++ b/netbox/dcim/management/commands/trace_paths.py @@ -60,7 +60,7 @@ class Command(BaseCommand): self.stdout.write((self.style.SUCCESS(f' Deleted {deleted_count} paths'))) # Reinitialize the model's PK sequence - self.stdout.write(f'Resetting database sequence for CablePath model') + self.stdout.write('Resetting database sequence for CablePath model') sequence_sql = connection.ops.sequence_reset_sql(no_style(), [CablePath]) with connection.cursor() as cursor: for sql in sequence_sql: diff --git a/netbox/dcim/models/device_component_templates.py b/netbox/dcim/models/device_component_templates.py index 5f6aa08e3..3a71c424d 100644 --- a/netbox/dcim/models/device_component_templates.py +++ b/netbox/dcim/models/device_component_templates.py @@ -160,7 +160,6 @@ class ModularComponentTemplateModel(ComponentTemplateModel): def _get_module_tree(self, module): modules = [] - all_module_bays = module.device.modulebays.all().select_related('module') while module: modules.append(module) if module.module_bay: diff --git a/netbox/dcim/tables/devicetypes.py b/netbox/dcim/tables/devicetypes.py index 69ff8b3a2..e8a4e35f1 100644 --- a/netbox/dcim/tables/devicetypes.py +++ b/netbox/dcim/tables/devicetypes.py @@ -1,6 +1,5 @@ -from django.utils.translation import gettext_lazy as _ import django_tables2 as tables -from django.utils.translation import gettext as _ +from django.utils.translation import gettext_lazy as _ from dcim import models from netbox.tables import NetBoxTable, columns diff --git a/netbox/dcim/tests/test_api.py b/netbox/dcim/tests/test_api.py index 573fdbb96..1b460cd59 100644 --- a/netbox/dcim/tests/test_api.py +++ b/netbox/dcim/tests/test_api.py @@ -2135,12 +2135,12 @@ class ConnectedDeviceTest(APITestCase): def test_get_connected_device(self): url = reverse('dcim-api:connected-device-list') - url_params = f'?peer_device=TestDevice1&peer_interface=eth0' + url_params = '?peer_device=TestDevice1&peer_interface=eth0' response = self.client.get(url + url_params, **self.header) self.assertHttpStatus(response, status.HTTP_200_OK) self.assertEqual(response.data['name'], 'TestDevice2') - url_params = f'?peer_device=TestDevice1&peer_interface=eth1' + url_params = '?peer_device=TestDevice1&peer_interface=eth1' response = self.client.get(url + url_params, **self.header) self.assertHttpStatus(response, status.HTTP_404_NOT_FOUND) diff --git a/netbox/dcim/tests/test_filtersets.py b/netbox/dcim/tests/test_filtersets.py index afb360d76..6c65cad93 100644 --- a/netbox/dcim/tests/test_filtersets.py +++ b/netbox/dcim/tests/test_filtersets.py @@ -4838,13 +4838,6 @@ class InventoryItemTestCase(TestCase, ChangeLoggedFilterSetTests): params = {'device_role': [role[0].slug, role[1].slug]} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) - def test_role(self): - role = DeviceRole.objects.all()[:2] - params = {'role_id': [role[0].pk, role[1].pk]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) - params = {'role': [role[0].slug, role[1].slug]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) - def test_device(self): devices = Device.objects.all()[:2] params = {'device_id': [devices[0].pk, devices[1].pk]} diff --git a/netbox/dcim/tests/test_models.py b/netbox/dcim/tests/test_models.py index 1c3dbb90b..70431c2e1 100644 --- a/netbox/dcim/tests/test_models.py +++ b/netbox/dcim/tests/test_models.py @@ -662,10 +662,8 @@ class ModuleBayTestCase(TestCase): def test_module_bay_recursion(self): module_bay_1 = ModuleBay.objects.get(name='Module Bay 1') - module_bay_2 = ModuleBay.objects.get(name='Module Bay 2') module_bay_3 = ModuleBay.objects.get(name='Module Bay 3') module_1 = Module.objects.get(module_bay=module_bay_1) - module_2 = Module.objects.get(module_bay=module_bay_2) module_3 = Module.objects.get(module_bay=module_bay_3) # Confirm error if ModuleBay recurses @@ -681,8 +679,6 @@ class ModuleBayTestCase(TestCase): module_1.save() def test_single_module_token(self): - module_bays = ModuleBay.objects.all() - modules = Module.objects.all() device_type = DeviceType.objects.first() device_role = DeviceRole.objects.first() site = Site.objects.first() @@ -708,7 +704,7 @@ class ModuleBayTestCase(TestCase): location=location, rack=rack ) - cp = device.consoleports.first() + device.consoleports.first() def test_nested_module_token(self): pass @@ -733,39 +729,41 @@ class CableTestCase(TestCase): device2 = Device.objects.create( device_type=devicetype, role=role, name='TestDevice2', site=site ) - interface1 = Interface.objects.create(device=device1, name='eth0') - interface2 = Interface.objects.create(device=device2, name='eth0') - interface3 = Interface.objects.create(device=device2, name='eth1') - Cable(a_terminations=[interface1], b_terminations=[interface2]).save() + interfaces = ( + Interface(device=device1, name='eth0'), + Interface(device=device2, name='eth0'), + Interface(device=device2, name='eth1'), + ) + Interface.objects.bulk_create(interfaces) + Cable(a_terminations=[interfaces[0]], b_terminations=[interfaces[1]]).save() + PowerPort.objects.create(device=device2, name='psu1') - power_port1 = PowerPort.objects.create(device=device2, name='psu1') - patch_pannel = Device.objects.create( + patch_panel = Device.objects.create( device_type=devicetype, role=role, name='TestPatchPanel', site=site ) - rear_port1 = RearPort.objects.create(device=patch_pannel, name='RP1', type='8p8c') - front_port1 = FrontPort.objects.create( - device=patch_pannel, name='FP1', type='8p8c', rear_port=rear_port1, rear_port_position=1 + rear_ports = ( + RearPort(device=patch_panel, name='RP1', type='8p8c'), + RearPort(device=patch_panel, name='RP2', type='8p8c', positions=2), + RearPort(device=patch_panel, name='RP3', type='8p8c', positions=3), + RearPort(device=patch_panel, name='RP4', type='8p8c', positions=3), ) - rear_port2 = RearPort.objects.create(device=patch_pannel, name='RP2', type='8p8c', positions=2) - front_port2 = FrontPort.objects.create( - device=patch_pannel, name='FP2', type='8p8c', rear_port=rear_port2, rear_port_position=1 - ) - rear_port3 = RearPort.objects.create(device=patch_pannel, name='RP3', type='8p8c', positions=3) - front_port3 = FrontPort.objects.create( - device=patch_pannel, name='FP3', type='8p8c', rear_port=rear_port3, rear_port_position=1 - ) - rear_port4 = RearPort.objects.create(device=patch_pannel, name='RP4', type='8p8c', positions=3) - front_port4 = FrontPort.objects.create( - device=patch_pannel, name='FP4', type='8p8c', rear_port=rear_port4, rear_port_position=1 + RearPort.objects.bulk_create(rear_ports) + front_ports = ( + FrontPort(device=patch_panel, name='FP1', type='8p8c', rear_port=rear_ports[0], rear_port_position=1), + FrontPort(device=patch_panel, name='FP2', type='8p8c', rear_port=rear_ports[1], rear_port_position=1), + FrontPort(device=patch_panel, name='FP3', type='8p8c', rear_port=rear_ports[2], rear_port_position=1), + FrontPort(device=patch_panel, name='FP4', type='8p8c', rear_port=rear_ports[3], rear_port_position=1), ) + FrontPort.objects.bulk_create(front_ports) + provider = Provider.objects.create(name='Provider 1', slug='provider-1') provider_network = ProviderNetwork.objects.create(name='Provider Network 1', provider=provider) circuittype = CircuitType.objects.create(name='Circuit Type 1', slug='circuit-type-1') circuit1 = Circuit.objects.create(provider=provider, type=circuittype, cid='1') circuit2 = Circuit.objects.create(provider=provider, type=circuittype, cid='2') - circuittermination1 = CircuitTermination.objects.create(circuit=circuit1, site=site, term_side='A') - circuittermination2 = CircuitTermination.objects.create(circuit=circuit1, site=site, term_side='Z') - circuittermination3 = CircuitTermination.objects.create(circuit=circuit2, provider_network=provider_network, term_side='A') + CircuitTermination.objects.create(circuit=circuit1, site=site, term_side='A') + CircuitTermination.objects.create(circuit=circuit1, site=site, term_side='Z') + CircuitTermination.objects.create(circuit=circuit2, provider_network=provider_network, term_side='A') def test_cable_creation(self): """ diff --git a/netbox/dcim/tests/test_views.py b/netbox/dcim/tests/test_views.py index 7d6c34337..e290a6d1d 100644 --- a/netbox/dcim/tests/test_views.py +++ b/netbox/dcim/tests/test_views.py @@ -2571,7 +2571,7 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase): } cls.csv_data = ( - f"device,name,type,vrf.pk,poe_mode,poe_type", + "device,name,type,vrf.pk,poe_mode,poe_type", f"Device 1,Interface 4,1000base-t,{vrfs[0].pk},pse,type1-ieee802.3af", f"Device 1,Interface 5,1000base-t,{vrfs[0].pk},pse,type1-ieee802.3af", f"Device 1,Interface 6,1000base-t,{vrfs[0].pk},pse,type1-ieee802.3af", diff --git a/netbox/extras/api/nested_serializers.py b/netbox/extras/api/nested_serializers.py index ba291b34e..235cdd6d6 100644 --- a/netbox/extras/api/nested_serializers.py +++ b/netbox/extras/api/nested_serializers.py @@ -24,7 +24,7 @@ __all__ = [ # TODO: Remove in v4.2 warnings.warn( - f"Dedicated nested serializers will be removed in NetBox v4.2. Use Serializer(nested=True) instead.", + "Dedicated nested serializers will be removed in NetBox v4.2. Use Serializer(nested=True) instead.", DeprecationWarning ) diff --git a/netbox/extras/jobs.py b/netbox/extras/jobs.py index e540ef439..64a7d6a69 100644 --- a/netbox/extras/jobs.py +++ b/netbox/extras/jobs.py @@ -48,7 +48,7 @@ class ScriptJob(JobRunner): except AbortTransaction: script.log_info(message=_("Database changes have been reverted automatically.")) if script.failed: - logger.warning(f"Script failed") + logger.warning("Script failed") raise except Exception as e: diff --git a/netbox/extras/management/commands/housekeeping.py b/netbox/extras/management/commands/housekeeping.py index cb8137ee2..ade486fc0 100644 --- a/netbox/extras/management/commands/housekeeping.py +++ b/netbox/extras/management/commands/housekeeping.py @@ -95,7 +95,7 @@ class Command(BaseCommand): self.stdout.write("[*] Checking for latest release") if settings.ISOLATED_DEPLOYMENT: if options['verbosity']: - self.stdout.write(f"\tSkipping: ISOLATED_DEPLOYMENT is enabled") + self.stdout.write("\tSkipping: ISOLATED_DEPLOYMENT is enabled") elif settings.RELEASE_CHECK_URL: headers = { 'Accept': 'application/vnd.github.v3+json', @@ -129,7 +129,7 @@ class Command(BaseCommand): self.stdout.write(f"\tRequest error: {exc}", self.style.ERROR) else: if options['verbosity']: - self.stdout.write(f"\tSkipping: RELEASE_CHECK_URL not set") + self.stdout.write("\tSkipping: RELEASE_CHECK_URL not set") if options['verbosity']: self.stdout.write("Finished.", self.style.SUCCESS) diff --git a/netbox/extras/management/commands/reindex.py b/netbox/extras/management/commands/reindex.py index 5aab74511..21442be93 100644 --- a/netbox/extras/management/commands/reindex.py +++ b/netbox/extras/management/commands/reindex.py @@ -96,9 +96,9 @@ class Command(BaseCommand): if i: self.stdout.write(f'{i} entries cached.') else: - self.stdout.write(f'No objects found.') + self.stdout.write('No objects found.') - msg = f'Completed.' + msg = 'Completed.' if total_count := search_backend.size: msg += f' Total entries: {total_count}' self.stdout.write(msg, self.style.SUCCESS) diff --git a/netbox/extras/management/commands/runscript.py b/netbox/extras/management/commands/runscript.py index ab0d6d894..d5fb435ad 100644 --- a/netbox/extras/management/commands/runscript.py +++ b/netbox/extras/management/commands/runscript.py @@ -51,7 +51,7 @@ class Command(BaseCommand): user = User.objects.filter(is_superuser=True).order_by('pk')[0] # Setup logging to Stdout - formatter = logging.Formatter(f'[%(asctime)s][%(levelname)s] - %(message)s') + formatter = logging.Formatter('[%(asctime)s][%(levelname)s] - %(message)s') stdouthandler = logging.StreamHandler(sys.stdout) stdouthandler.setLevel(logging.DEBUG) stdouthandler.setFormatter(formatter) diff --git a/netbox/extras/models/customfields.py b/netbox/extras/models/customfields.py index 91de7df0d..8b7fc0cb6 100644 --- a/netbox/extras/models/customfields.py +++ b/netbox/extras/models/customfields.py @@ -283,7 +283,7 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel): """ for ct in content_types: model = ct.model_class() - instances = model.objects.exclude(**{f'custom_field_data__contains': self.name}) + instances = model.objects.exclude(**{'custom_field_data__contains': self.name}) for instance in instances: instance.custom_field_data[self.name] = self.default model.objects.bulk_update(instances, ['custom_field_data'], batch_size=100) diff --git a/netbox/extras/scripts.py b/netbox/extras/scripts.py index 96d08d8af..f2bd75a1d 100644 --- a/netbox/extras/scripts.py +++ b/netbox/extras/scripts.py @@ -554,7 +554,7 @@ class BaseScript: """ Run the report and save its results. Each test method will be executed in order. """ - self.logger.info(f"Running report") + self.logger.info("Running report") try: for test_name in self.tests: diff --git a/netbox/extras/tests/test_customvalidators.py b/netbox/extras/tests/test_customvalidators.py index 49deb9da5..9f85b4913 100644 --- a/netbox/extras/tests/test_customvalidators.py +++ b/netbox/extras/tests/test_customvalidators.py @@ -162,7 +162,7 @@ class CustomValidatorTest(TestCase): Site(name='abcdef123', slug='abcdef123').clean() @override_settings(CUSTOM_VALIDATORS={'dcim.site': [region_validator]}) - def test_valid(self): + def test_related_object(self): region1 = Region(name='Foo', slug='foo') region1.save() region2 = Region(name='Bar', slug='bar') diff --git a/netbox/extras/tests/test_models.py b/netbox/extras/tests/test_models.py index c92a1bc54..188a06a3f 100644 --- a/netbox/extras/tests/test_models.py +++ b/netbox/extras/tests/test_models.py @@ -49,11 +49,11 @@ class ConfigContextTest(TestCase): sitegroup = SiteGroup.objects.create(name='Site Group') site = Site.objects.create(name='Site 1', slug='site-1', region=region, group=sitegroup) location = Location.objects.create(name='Location 1', slug='location-1', site=site) - platform = Platform.objects.create(name='Platform') + Platform.objects.create(name='Platform') tenantgroup = TenantGroup.objects.create(name='Tenant Group') - tenant = Tenant.objects.create(name='Tenant', group=tenantgroup) - tag1 = Tag.objects.create(name='Tag', slug='tag') - tag2 = Tag.objects.create(name='Tag2', slug='tag2') + Tenant.objects.create(name='Tenant', group=tenantgroup) + Tag.objects.create(name='Tag', slug='tag') + Tag.objects.create(name='Tag2', slug='tag2') Device.objects.create( name='Device 1', diff --git a/netbox/extras/tests/test_views.py b/netbox/extras/tests/test_views.py index 796d36180..5d82fae4c 100644 --- a/netbox/extras/tests/test_views.py +++ b/netbox/extras/tests/test_views.py @@ -417,7 +417,7 @@ class EventRulesTestCase(ViewTestCases.PrimaryObjectViewTestCase): } cls.csv_data = ( - f'name,object_types,event_types,action_type,action_object', + 'name,object_types,event_types,action_type,action_object', f'Webhook 4,dcim.site,"{OBJECT_CREATED},{OBJECT_UPDATED}",webhook,Webhook 1', ) diff --git a/netbox/ipam/api/nested_serializers.py b/netbox/ipam/api/nested_serializers.py index 57a1a65d5..8b10f29df 100644 --- a/netbox/ipam/api/nested_serializers.py +++ b/netbox/ipam/api/nested_serializers.py @@ -30,7 +30,7 @@ __all__ = [ # TODO: Remove in v4.2 warnings.warn( - f"Dedicated nested serializers will be removed in NetBox v4.2. Use Serializer(nested=True) instead.", + "Dedicated nested serializers will be removed in NetBox v4.2. Use Serializer(nested=True) instead.", DeprecationWarning ) diff --git a/netbox/ipam/api/views.py b/netbox/ipam/api/views.py index cac90bb87..ffd4d5b7d 100644 --- a/netbox/ipam/api/views.py +++ b/netbox/ipam/api/views.py @@ -186,13 +186,13 @@ class AvailableObjectsView(ObjectValidationMixin, APIView): """ Return the parent object. """ - raise NotImplemented() + raise NotImplementedError() def get_available_objects(self, parent, limit=None): """ Return all available objects for the parent. """ - raise NotImplemented() + raise NotImplementedError() def get_extra_context(self, parent): """ @@ -250,7 +250,7 @@ class AvailableObjectsView(ObjectValidationMixin, APIView): # Determine if the requested number of objects is available if not self.check_sufficient_available(serializer.validated_data, available_objects): return Response( - {"detail": f"Insufficient resources are available to satisfy the request"}, + {"detail": "Insufficient resources are available to satisfy the request"}, status=status.HTTP_409_CONFLICT ) diff --git a/netbox/ipam/graphql/mixins.py b/netbox/ipam/graphql/mixins.py index 757e62c74..692741871 100644 --- a/netbox/ipam/graphql/mixins.py +++ b/netbox/ipam/graphql/mixins.py @@ -10,9 +10,9 @@ __all__ = ( @strawberry.type class IPAddressesMixin: - ip_addresses: List[Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')]] + ip_addresses: List[Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')]] # noqa: F821 @strawberry.type class VLANGroupsMixin: - vlan_groups: List[Annotated["VLANGroupType", strawberry.lazy('ipam.graphql.types')]] + vlan_groups: List[Annotated["VLANGroupType", strawberry.lazy('ipam.graphql.types')]] # noqa: F821 diff --git a/netbox/ipam/tests/test_api.py b/netbox/ipam/tests/test_api.py index 59335cbbe..1d2cdf1b7 100644 --- a/netbox/ipam/tests/test_api.py +++ b/netbox/ipam/tests/test_api.py @@ -700,8 +700,6 @@ class IPAddressTest(APIViewTestCases.APIViewTestCase): device1.primary_ip4 = ip_addresses[0] device1.save() - ip2 = ip_addresses[1] - url = reverse('ipam-api:ipaddress-detail', kwargs={'pk': ip1.pk}) self.add_permissions('ipam.change_ipaddress') diff --git a/netbox/ipam/tests/test_views.py b/netbox/ipam/tests/test_views.py index 2acb80ac1..95b311878 100644 --- a/netbox/ipam/tests/test_views.py +++ b/netbox/ipam/tests/test_views.py @@ -50,7 +50,7 @@ class ASNRangeTestCase(ViewTestCases.PrimaryObjectViewTestCase): } cls.csv_data = ( - f"name,slug,rir,tenant,start,end,description", + "name,slug,rir,tenant,start,end,description", f"ASN Range 4,asn-range-4,{rirs[1].name},{tenants[1].name},400,499,Fourth range", f"ASN Range 5,asn-range-5,{rirs[1].name},{tenants[1].name},500,599,Fifth range", f"ASN Range 6,asn-range-6,{rirs[1].name},{tenants[1].name},600,699,Sixth range", @@ -770,14 +770,14 @@ class VLANGroupTestCase(ViewTestCases.OrganizationalObjectViewTestCase): } cls.csv_data = ( - f"name,slug,scope_type,scope_id,description", - f"VLAN Group 4,vlan-group-4,,,Fourth VLAN group", + "name,slug,scope_type,scope_id,description", + "VLAN Group 4,vlan-group-4,,,Fourth VLAN group", f"VLAN Group 5,vlan-group-5,dcim.site,{sites[0].pk},Fifth VLAN group", f"VLAN Group 6,vlan-group-6,dcim.site,{sites[1].pk},Sixth VLAN group", ) cls.csv_update_data = ( - f"id,name,description", + "id,name,description", f"{vlan_groups[0].pk},VLAN Group 7,Fourth VLAN group7", f"{vlan_groups[1].pk},VLAN Group 8,Fifth VLAN group8", f"{vlan_groups[2].pk},VLAN Group 9,Sixth VLAN group9", diff --git a/netbox/netbox/config/__init__.py b/netbox/netbox/config/__init__.py index 1c16d6769..23108f1d2 100644 --- a/netbox/netbox/config/__init__.py +++ b/netbox/netbox/config/__init__.py @@ -85,7 +85,7 @@ class Config: logger.debug("Loaded configuration data from database") except DatabaseError: # The database may not be available yet (e.g. when running a management command) - logger.warning(f"Skipping config initialization (database unavailable)") + logger.warning("Skipping config initialization (database unavailable)") return revision.activate() diff --git a/netbox/netbox/data_backends.py b/netbox/netbox/data_backends.py index d5bab75c1..e3a3de4d1 100644 --- a/netbox/netbox/data_backends.py +++ b/netbox/netbox/data_backends.py @@ -50,4 +50,4 @@ class DataBackend: 2. Yields the local path at which data has been replicated 3. Performs any necessary cleanup """ - raise NotImplemented() + raise NotImplementedError() diff --git a/netbox/netbox/navigation/menu.py b/netbox/netbox/navigation/menu.py index a1d65d6e2..9d8ffaaf8 100644 --- a/netbox/netbox/navigation/menu.py +++ b/netbox/netbox/navigation/menu.py @@ -386,57 +386,57 @@ ADMIN_MENU = Menu( label=_('Authentication'), items=( MenuItem( - link=f'users:user_list', + link='users:user_list', link_text=_('Users'), auth_required=True, - permissions=[f'users.view_user'], + permissions=['users.view_user'], buttons=( MenuItemButton( - link=f'users:user_add', + link='users:user_add', title='Add', icon_class='mdi mdi-plus-thick', - permissions=[f'users.add_user'] + permissions=['users.add_user'] ), MenuItemButton( - link=f'users:user_import', + link='users:user_import', title='Import', icon_class='mdi mdi-upload', - permissions=[f'users.add_user'] + permissions=['users.add_user'] ) ) ), MenuItem( - link=f'users:group_list', + link='users:group_list', link_text=_('Groups'), auth_required=True, - permissions=[f'users.view_group'], + permissions=['users.view_group'], buttons=( MenuItemButton( - link=f'users:group_add', + link='users:group_add', title='Add', icon_class='mdi mdi-plus-thick', - permissions=[f'users.add_group'] + permissions=['users.add_group'] ), MenuItemButton( - link=f'users:group_import', + link='users:group_import', title='Import', icon_class='mdi mdi-upload', - permissions=[f'users.add_group'] + permissions=['users.add_group'] ) ) ), MenuItem( - link=f'users:token_list', + link='users:token_list', link_text=_('API Tokens'), auth_required=True, - permissions=[f'users.view_token'], + permissions=['users.view_token'], buttons=get_model_buttons('users', 'token') ), MenuItem( - link=f'users:objectpermission_list', + link='users:objectpermission_list', link_text=_('Permissions'), auth_required=True, - permissions=[f'users.view_objectpermission'], + permissions=['users.view_objectpermission'], buttons=get_model_buttons('users', 'objectpermission', actions=['add']) ), ), diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 9c5078ccb..a8ac68d4d 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -198,7 +198,7 @@ if len(SECRET_KEY) < 50: if RELEASE_CHECK_URL: try: URLValidator()(RELEASE_CHECK_URL) - except ValidationError as e: + except ValidationError: raise ImproperlyConfigured( "RELEASE_CHECK_URL must be a valid URL. Example: https://api.github.com/repos/netbox-community/netbox" ) diff --git a/netbox/netbox/staging.py b/netbox/netbox/staging.py index 4d37fb7ad..e6b946403 100644 --- a/netbox/netbox/staging.py +++ b/netbox/netbox/staging.py @@ -80,7 +80,7 @@ class checkout: Create Change instances for all actions stored in the queue. """ if not self.queue: - logger.debug(f"No queued changes; aborting") + logger.debug("No queued changes; aborting") return logger.debug(f"Processing {len(self.queue)} queued changes") diff --git a/netbox/netbox/tests/dummy_plugin/views.py b/netbox/netbox/tests/dummy_plugin/views.py index c4d80731f..82f250fc1 100644 --- a/netbox/netbox/tests/dummy_plugin/views.py +++ b/netbox/netbox/tests/dummy_plugin/views.py @@ -21,7 +21,7 @@ class DummyModelsView(View): class DummyModelAddView(View): def get(self, request): - return HttpResponse(f"Create an instance") + return HttpResponse("Create an instance") def post(self, request): instance = DummyModel( @@ -29,7 +29,7 @@ class DummyModelAddView(View): number=random.randint(1, 100000) ) instance.save() - return HttpResponse(f"Instance created") + return HttpResponse("Instance created") @register_model_view(Site, 'extra', path='other-stuff') diff --git a/netbox/netbox/tests/test_authentication.py b/netbox/netbox/tests/test_authentication.py index 5c7a30dc7..ae6d3f4c2 100644 --- a/netbox/netbox/tests/test_authentication.py +++ b/netbox/netbox/tests/test_authentication.py @@ -106,7 +106,7 @@ class ExternalAuthenticationTestCase(TestCase): self.assertEqual(settings.REMOTE_AUTH_HEADER, 'HTTP_REMOTE_USER') # Client should not be authenticated - response = self.client.get(reverse('home'), follow=True, **headers) + self.client.get(reverse('home'), follow=True, **headers) self.assertNotIn('_auth_user_id', self.client.session) @override_settings( diff --git a/netbox/netbox/tests/test_import.py b/netbox/netbox/tests/test_import.py index 03690029c..16711ef72 100644 --- a/netbox/netbox/tests/test_import.py +++ b/netbox/netbox/tests/test_import.py @@ -77,7 +77,6 @@ class CSVImportTestCase(ModelViewTestCase): self.assertHttpStatus(self.client.post(self._get_url('import'), data), 302) regions = Region.objects.all() self.assertEqual(regions.count(), 4) - region = Region.objects.get(slug="region-4") self.assertEqual( list(regions[0].tags.values_list('name', flat=True)), ['Alpha', 'Bravo'] diff --git a/netbox/tenancy/api/nested_serializers.py b/netbox/tenancy/api/nested_serializers.py index 5f339ecba..5adb78863 100644 --- a/netbox/tenancy/api/nested_serializers.py +++ b/netbox/tenancy/api/nested_serializers.py @@ -15,7 +15,7 @@ __all__ = [ # TODO: Remove in v4.2 warnings.warn( - f"Dedicated nested serializers will be removed in NetBox v4.2. Use Serializer(nested=True) instead.", + "Dedicated nested serializers will be removed in NetBox v4.2. Use Serializer(nested=True) instead.", DeprecationWarning ) diff --git a/netbox/tenancy/graphql/mixins.py b/netbox/tenancy/graphql/mixins.py index 2d97ba718..9cdba100e 100644 --- a/netbox/tenancy/graphql/mixins.py +++ b/netbox/tenancy/graphql/mixins.py @@ -10,4 +10,4 @@ __all__ = ( @strawberry.type class ContactAssignmentsMixin: - assignments: List[Annotated["ContactAssignmentType", strawberry.lazy('tenancy.graphql.types')]] + assignments: List[Annotated["ContactAssignmentType", strawberry.lazy('tenancy.graphql.types')]] # noqa: F821 diff --git a/netbox/users/api/nested_serializers.py b/netbox/users/api/nested_serializers.py index 2a5763476..201e38901 100644 --- a/netbox/users/api/nested_serializers.py +++ b/netbox/users/api/nested_serializers.py @@ -18,7 +18,7 @@ __all__ = [ # TODO: Remove in v4.2 warnings.warn( - f"Dedicated nested serializers will be removed in NetBox v4.2. Use Serializer(nested=True) instead.", + "Dedicated nested serializers will be removed in NetBox v4.2. Use Serializer(nested=True) instead.", DeprecationWarning ) diff --git a/netbox/users/api/views.py b/netbox/users/api/views.py index 240f68d36..bba9a4ec3 100644 --- a/netbox/users/api/views.py +++ b/netbox/users/api/views.py @@ -73,7 +73,7 @@ class TokenProvisionView(APIView): def perform_create(self, serializer): model = serializer.Meta.model - logger = logging.getLogger(f'netbox.api.views.TokenProvisionView') + logger = logging.getLogger('netbox.api.views.TokenProvisionView') logger.info(f"Creating new {model._meta.verbose_name}") serializer.save() diff --git a/netbox/users/forms/model_forms.py b/netbox/users/forms/model_forms.py index 639b9f726..42c3b15f0 100644 --- a/netbox/users/forms/model_forms.py +++ b/netbox/users/forms/model_forms.py @@ -36,7 +36,6 @@ class UserConfigFormMetaclass(forms.models.ModelFormMetaclass): # Emulate a declared field for each supported user preference preference_fields = {} for field_name, preference in PREFERENCES.items(): - description = f'{preference.description}
' if preference.description else '' help_text = f'{field_name}' if preference.description: help_text = f'{preference.description}
{help_text}' diff --git a/netbox/users/tests/test_preferences.py b/netbox/users/tests/test_preferences.py index b5037ec3f..13120d328 100644 --- a/netbox/users/tests/test_preferences.py +++ b/netbox/users/tests/test_preferences.py @@ -51,11 +51,11 @@ class UserPreferencesTest(TestCase): # Check that table ordering preference has been recorded self.user.refresh_from_db() - ordering = self.user.config.get(f'tables.SiteTable.ordering') + ordering = self.user.config.get('tables.SiteTable.ordering') self.assertEqual(ordering, ['status']) # Check that a recorded preference is honored by default - self.user.config.set(f'tables.SiteTable.ordering', ['-status'], commit=True) + self.user.config.set('tables.SiteTable.ordering', ['-status'], commit=True) table = SiteTable(Site.objects.all()) request = RequestFactory().get(url) request.user = self.user diff --git a/netbox/utilities/forms/fields/dynamic.py b/netbox/utilities/forms/fields/dynamic.py index bec067ba2..6666c0e4d 100644 --- a/netbox/utilities/forms/fields/dynamic.py +++ b/netbox/utilities/forms/fields/dynamic.py @@ -142,7 +142,7 @@ class DynamicModelChoiceMixin: if data: # When the field is multiple choice pass the data as a list if it's not already - if isinstance(bound_field.field, DynamicModelMultipleChoiceField) and not type(data) is list: + if isinstance(bound_field.field, DynamicModelMultipleChoiceField) and type(data) is not list: data = [data] field_name = getattr(self, 'to_field_name') or 'pk' diff --git a/netbox/utilities/html.py b/netbox/utilities/html.py index f99dabe5a..c9203d169 100644 --- a/netbox/utilities/html.py +++ b/netbox/utilities/html.py @@ -59,7 +59,7 @@ def highlight(value, highlight, trim_pre=None, trim_post=None, trim_placeholder= else: highlight = re.escape(highlight) pre, match, post = re.split(fr'({highlight})', value, maxsplit=1, flags=re.IGNORECASE) - except ValueError as e: + except ValueError: # Match not found return escape(value) diff --git a/netbox/utilities/tests/test_api.py b/netbox/utilities/tests/test_api.py index 81be70a34..ba0c3c4f8 100644 --- a/netbox/utilities/tests/test_api.py +++ b/netbox/utilities/tests/test_api.py @@ -149,7 +149,7 @@ class APIPaginationTestCase(APITestCase): self.assertHttpStatus(response, status.HTTP_200_OK) self.assertEqual(response.data['count'], 100) - self.assertTrue(response.data['next'].endswith(f'?limit=10&offset=10')) + self.assertTrue(response.data['next'].endswith('?limit=10&offset=10')) self.assertIsNone(response.data['previous']) self.assertEqual(len(response.data['results']), 10) @@ -159,7 +159,7 @@ class APIPaginationTestCase(APITestCase): self.assertHttpStatus(response, status.HTTP_200_OK) self.assertEqual(response.data['count'], 100) - self.assertTrue(response.data['next'].endswith(f'?limit=20&offset=20')) + self.assertTrue(response.data['next'].endswith('?limit=20&offset=20')) self.assertIsNone(response.data['previous']) self.assertEqual(len(response.data['results']), 20) diff --git a/netbox/utilities/tests/test_counters.py b/netbox/utilities/tests/test_counters.py index b87e73ace..45823065e 100644 --- a/netbox/utilities/tests/test_counters.py +++ b/netbox/utilities/tests/test_counters.py @@ -85,7 +85,7 @@ class CountersTest(TestCase): def test_mptt_child_delete(self): device1, device2 = Device.objects.all() inventory_item1 = InventoryItem.objects.create(device=device1, name='Inventory Item 1') - inventory_item2 = InventoryItem.objects.create(device=device1, name='Inventory Item 2', parent=inventory_item1) + InventoryItem.objects.create(device=device1, name='Inventory Item 2', parent=inventory_item1) device1.refresh_from_db() self.assertEqual(device1.inventory_item_count, 2) diff --git a/netbox/virtualization/api/nested_serializers.py b/netbox/virtualization/api/nested_serializers.py index a2395faa5..59016f74d 100644 --- a/netbox/virtualization/api/nested_serializers.py +++ b/netbox/virtualization/api/nested_serializers.py @@ -18,7 +18,7 @@ __all__ = [ # TODO: Remove in v4.2 warnings.warn( - f"Dedicated nested serializers will be removed in NetBox v4.2. Use Serializer(nested=True) instead.", + "Dedicated nested serializers will be removed in NetBox v4.2. Use Serializer(nested=True) instead.", DeprecationWarning ) diff --git a/netbox/virtualization/tests/test_views.py b/netbox/virtualization/tests/test_views.py index 0daa55a5c..3c6a058c9 100644 --- a/netbox/virtualization/tests/test_views.py +++ b/netbox/virtualization/tests/test_views.py @@ -354,14 +354,14 @@ class VMInterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase): } cls.csv_data = ( - f"virtual_machine,name,vrf.pk", + "virtual_machine,name,vrf.pk", f"Virtual Machine 2,Interface 4,{vrfs[0].pk}", f"Virtual Machine 2,Interface 5,{vrfs[0].pk}", f"Virtual Machine 2,Interface 6,{vrfs[0].pk}", ) cls.csv_update_data = ( - f"id,name,description", + "id,name,description", f"{interfaces[0].pk},Interface 7,New description 7", f"{interfaces[1].pk},Interface 8,New description 8", f"{interfaces[2].pk},Interface 9,New description 9", @@ -438,14 +438,14 @@ class VirtualDiskTestCase(ViewTestCases.DeviceComponentViewTestCase): } cls.csv_data = ( - f"virtual_machine,name,size,description", - f"Virtual Machine 1,Disk 4,20,Fourth", - f"Virtual Machine 1,Disk 5,20,Fifth", - f"Virtual Machine 1,Disk 6,20,Sixth", + "virtual_machine,name,size,description", + "Virtual Machine 1,Disk 4,20,Fourth", + "Virtual Machine 1,Disk 5,20,Fifth", + "Virtual Machine 1,Disk 6,20,Sixth", ) cls.csv_update_data = ( - f"id,name,size", + "id,name,size", f"{disks[0].pk},disk1,20", f"{disks[1].pk},disk2,20", f"{disks[2].pk},disk3,20", diff --git a/netbox/virtualization/views.py b/netbox/virtualization/views.py index ad3487b8b..0828d3a2a 100644 --- a/netbox/virtualization/views.py +++ b/netbox/virtualization/views.py @@ -657,7 +657,7 @@ class VirtualMachineBulkAddInterfaceView(generic.BulkComponentCreateView): default_return_url = 'virtualization:virtualmachine_list' def get_required_permission(self): - return f'virtualization.add_vminterface' + return 'virtualization.add_vminterface' class VirtualMachineBulkAddVirtualDiskView(generic.BulkComponentCreateView): @@ -671,4 +671,4 @@ class VirtualMachineBulkAddVirtualDiskView(generic.BulkComponentCreateView): default_return_url = 'virtualization:virtualmachine_list' def get_required_permission(self): - return f'virtualization.add_virtualdisk' + return 'virtualization.add_virtualdisk' diff --git a/netbox/vpn/api/nested_serializers.py b/netbox/vpn/api/nested_serializers.py index c1a90cbea..59e394c2e 100644 --- a/netbox/vpn/api/nested_serializers.py +++ b/netbox/vpn/api/nested_serializers.py @@ -21,7 +21,7 @@ __all__ = ( # TODO: Remove in v4.2 warnings.warn( - f"Dedicated nested serializers will be removed in NetBox v4.2. Use Serializer(nested=True) instead.", + "Dedicated nested serializers will be removed in NetBox v4.2. Use Serializer(nested=True) instead.", DeprecationWarning ) diff --git a/netbox/vpn/filtersets.py b/netbox/vpn/filtersets.py index 92aa702c6..6403b662f 100644 --- a/netbox/vpn/filtersets.py +++ b/netbox/vpn/filtersets.py @@ -147,17 +147,6 @@ class IKEProposalFilterSet(NetBoxModelFilterSet): group = django_filters.MultipleChoiceFilter( choices=DHGroupChoices ) - ike_policy_id = django_filters.ModelMultipleChoiceFilter( - field_name='ike_policies', - queryset=IKEPolicy.objects.all(), - label=_('IKE policy (ID)'), - ) - ike_policy = django_filters.ModelMultipleChoiceFilter( - field_name='ike_policies__name', - queryset=IKEPolicy.objects.all(), - to_field_name='name', - label=_('IKE policy (name)'), - ) class Meta: model = IKEProposal diff --git a/netbox/vpn/tests/test_filtersets.py b/netbox/vpn/tests/test_filtersets.py index 0b9c79420..d2b893766 100644 --- a/netbox/vpn/tests/test_filtersets.py +++ b/netbox/vpn/tests/test_filtersets.py @@ -385,13 +385,6 @@ class IKEProposalTestCase(TestCase, ChangeLoggedFilterSetTests): params = {'sa_lifetime': [1000, 2000]} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) - def test_ike_policy(self): - ike_policies = IKEPolicy.objects.all()[:2] - params = {'ike_policy_id': [ike_policies[0].pk, ike_policies[1].pk]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) - params = {'ike_policy': [ike_policies[0].name, ike_policies[1].name]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) - class IKEPolicyTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = IKEPolicy.objects.all() diff --git a/netbox/vpn/tests/test_views.py b/netbox/vpn/tests/test_views.py index 105ca0b6f..05ac527fe 100644 --- a/netbox/vpn/tests/test_views.py +++ b/netbox/vpn/tests/test_views.py @@ -542,9 +542,9 @@ class IPSecProfileTestCase(ViewTestCases.PrimaryObjectViewTestCase): cls.csv_data = ( "name,mode,ike_policy,ipsec_policy", - f"IKE Proposal 4,ah,IKE Policy 2,IPSec Policy 2", - f"IKE Proposal 5,ah,IKE Policy 2,IPSec Policy 2", - f"IKE Proposal 6,ah,IKE Policy 2,IPSec Policy 2", + "IKE Proposal 4,ah,IKE Policy 2,IPSec Policy 2", + "IKE Proposal 5,ah,IKE Policy 2,IPSec Policy 2", + "IKE Proposal 6,ah,IKE Policy 2,IPSec Policy 2", ) cls.csv_update_data = ( @@ -661,7 +661,7 @@ class L2VPNTerminationTestCase( ) cls.csv_update_data = ( - f"id,l2vpn", + "id,l2vpn", f"{terminations[0].pk},{l2vpns[0].name}", f"{terminations[1].pk},{l2vpns[0].name}", f"{terminations[2].pk},{l2vpns[0].name}", diff --git a/netbox/wireless/api/nested_serializers.py b/netbox/wireless/api/nested_serializers.py index 433164e60..9b8b6c3e3 100644 --- a/netbox/wireless/api/nested_serializers.py +++ b/netbox/wireless/api/nested_serializers.py @@ -12,7 +12,7 @@ __all__ = ( # TODO: Remove in v4.2 warnings.warn( - f"Dedicated nested serializers will be removed in NetBox v4.2. Use Serializer(nested=True) instead.", + "Dedicated nested serializers will be removed in NetBox v4.2. Use Serializer(nested=True) instead.", DeprecationWarning ) diff --git a/netbox/wireless/tests/test_views.py b/netbox/wireless/tests/test_views.py index 055edf73c..a4dc2aae5 100644 --- a/netbox/wireless/tests/test_views.py +++ b/netbox/wireless/tests/test_views.py @@ -102,14 +102,14 @@ class WirelessLANTestCase(ViewTestCases.PrimaryObjectViewTestCase): } cls.csv_data = ( - f"group,ssid,status,tenant", + "group,ssid,status,tenant", f"Wireless LAN Group 2,WLAN4,{WirelessLANStatusChoices.STATUS_ACTIVE},{tenants[0].name}", f"Wireless LAN Group 2,WLAN5,{WirelessLANStatusChoices.STATUS_DISABLED},{tenants[1].name}", f"Wireless LAN Group 2,WLAN6,{WirelessLANStatusChoices.STATUS_RESERVED},{tenants[2].name}", ) cls.csv_update_data = ( - f"id,ssid", + "id,ssid", f"{wireless_lans[0].pk},WLAN7", f"{wireless_lans[1].pk},WLAN8", f"{wireless_lans[2].pk},WLAN9", @@ -167,7 +167,7 @@ class WirelessLinkTestCase(ViewTestCases.PrimaryObjectViewTestCase): } cls.csv_data = ( - f"interface_a,interface_b,status,tenant", + "interface_a,interface_b,status,tenant", f"{interfaces[6].pk},{interfaces[7].pk},connected,{tenants[0].name}", f"{interfaces[8].pk},{interfaces[9].pk},connected,{tenants[1].name}", f"{interfaces[10].pk},{interfaces[11].pk},connected,{tenants[2].name}", diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 000000000..854404469 --- /dev/null +++ b/ruff.toml @@ -0,0 +1,2 @@ +[lint] +ignore = ["E501", "F403", "F405"] diff --git a/scripts/git-hooks/pre-commit b/scripts/git-hooks/pre-commit index 2ccf8df89..4b4ca172f 100755 --- a/scripts/git-hooks/pre-commit +++ b/scripts/git-hooks/pre-commit @@ -28,8 +28,8 @@ if [ ${NOVALIDATE} ]; then exit $EXIT fi -echo "Validating PEP8 compliance..." -pycodestyle --ignore=W504,E501 --exclude=node_modules netbox/ +echo "Linting with ruff..." +ruff check netbox/ if [ $? != 0 ]; then EXIT=1 fi