Merge branch 'develop' into 7336-vlan-translation

This commit is contained in:
Brian Tiemann 2024-10-11 08:17:25 -04:00
commit 0bb0a2b818
122 changed files with 477 additions and 525 deletions

View File

@ -24,6 +24,21 @@ body:
- Data model extension
- New functionality
- Change to existing functionality
- Other
validations:
required: true
- type: dropdown
attributes:
label: Triage priority
description: >
Issue triage may be prioritized in some cases. Select whichever of the following
conditions applies, if any.
options:
- I volunteer to perform this work (if approved)
- I'm a NetBox Labs customer
- This is a very minor change
- N/A
default: 3
validations:
required: true
- type: textarea

View File

@ -22,6 +22,20 @@ body:
- Self-hosted
validations:
required: true
- type: dropdown
attributes:
label: Triage priority
description: >
Issue triage may be prioritized in some cases. Select whichever of the following
conditions applies, if any.
options:
- I volunteer to perform this work (if approved)
- I'm a NetBox Labs customer
- This is preventing me from using NetBox
- N/A
default: 3
validations:
required: true
- type: input
attributes:
label: NetBox Version

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -28,6 +28,7 @@ The technology employed in forming and operating the L2VPN. Choices include:
* VXLAN-EVPN
* MPLS-EVPN
* PBB-EVPN
* EVPN-VPWS
!!! note
Designating the type as VPWS, EPL, EP-LAN, EP-TREE will limit the L2VPN instance to two terminations.

View File

@ -7,13 +7,16 @@
* [#11671](https://github.com/netbox-community/netbox/issues/11671) - Display device's rack position in cable traces
* [#15829](https://github.com/netbox-community/netbox/issues/15829) - Rename Microsoft Azure AD SSO backend to Microsoft Entra ID
* [#17079](https://github.com/netbox-community/netbox/issues/17079) - Introduce additional choices for device airflow direction
* [#17216](https://github.com/netbox-community/netbox/issues/17216) - Add EVPN-VPWS L2VPN type
* [#17655](https://github.com/netbox-community/netbox/issues/17655) - Limit the display of tagged VLANs within interface tables
* [#17669](https://github.com/netbox-community/netbox/issues/17669) - Enable filtering VLANs by assigned device or VM interface
### Bug Fixes
* [#16024](https://github.com/netbox-community/netbox/issues/16024) - Fix AND/OR filtering in GraphQL API for selection fields
* [#17562](https://github.com/netbox-community/netbox/issues/17562) - Fix GraphQL API query support for custom field choices
* [#17566](https://github.com/netbox-community/netbox/issues/17566) - Fix AttributeError exception resulting from background jobs with no associated object type
* [#17636](https://github.com/netbox-community/netbox/issues/17636) - Fix filtering of related objects when adding a power port, rear port, or inventory item template to a device type
* [#17648](https://github.com/netbox-community/netbox/issues/17648) - Fix AttributeError exception when attempting to delete a background job under certain conditions
* [#17663](https://github.com/netbox-community/netbox/issues/17663) - Fix extended lookups for choice field filters
* [#17671](https://github.com/netbox-community/netbox/issues/17671) - Fix the display of rack types in global search results

View File

@ -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
)

View File

@ -7,7 +7,7 @@ class CircuitsConfig(AppConfig):
def ready(self):
from netbox.models.features import register_models
from . import signals, search
from . import signals, search # noqa: F401
# Register models
register_models(*self.get_models())

View File

@ -1,5 +1,4 @@
from django import forms
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _
from circuits.choices import *

View File

@ -1,7 +1,6 @@
import strawberry
import strawberry_django
from circuits import filtersets, models
from circuits import filtersets, models
from netbox.graphql.filter_mixins import autotype_decorator, BaseFilterMixin
__all__ = (

View File

@ -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}",

View File

@ -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
)

View File

@ -8,10 +8,8 @@ from drf_spectacular.plumbing import (
build_basic_type, build_choice_field, build_media_type_object, build_object_type, get_doc,
)
from drf_spectacular.types import OpenApiTypes
from rest_framework import serializers
from rest_framework.relations import ManyRelatedField
from netbox.api.fields import ChoiceField, SerializedPKRelatedField
from netbox.api.fields import ChoiceField
from netbox.api.serializers import WritableNestedSerializer
# see netbox.api.routers.NetBoxRouter

View File

@ -1,5 +1,3 @@
from rest_framework import serializers
from core.choices import *
from core.models import Job
from netbox.api.fields import ChoiceField, ContentTypeField

View File

@ -16,9 +16,9 @@ class CoreConfig(AppConfig):
name = "core"
def ready(self):
from core.api import schema # noqa
from core.api import schema # noqa: F401
from netbox.models.features import register_models
from . import data_backends, events, search
from . import data_backends, events, search # noqa: F401
# Register models
register_models(*self.get_models())

View File

@ -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

View File

@ -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,

View File

@ -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.")

View File

@ -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):
@ -201,7 +201,7 @@ class DataSource(JobsMixin, PrimaryModel):
logger.debug(f"Updated {updated_count} files")
# Bulk delete deleted files
deleted_count, _ = DataFile.objects.filter(pk__in=deleted_file_ids).delete()
deleted_count, __ = DataFile.objects.filter(pk__in=deleted_file_ids).delete()
logger.debug(f"Deleted {deleted_count} files")
# Walk the local replication to find new files

View File

@ -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):

View File

@ -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
)

View File

@ -1,5 +1,3 @@
from rest_framework import serializers
from dcim.models import Manufacturer
from netbox.api.fields import RelatedObjectCountField
from netbox.api.serializers import NetBoxModelSerializer

View File

@ -1,5 +1,3 @@
from rest_framework import serializers
from dcim.models import Platform
from extras.api.serializers_.configtemplates import ConfigTemplateSerializer
from netbox.api.fields import RelatedObjectCountField

View File

@ -1,5 +1,3 @@
from rest_framework import serializers
from dcim.choices import *
from dcim.models import PowerFeed, PowerPanel
from netbox.api.fields import ChoiceField, RelatedObjectCountField

View File

@ -1,5 +1,3 @@
from rest_framework import serializers
from dcim.models import DeviceRole, InventoryItemRole
from extras.api.serializers_.configtemplates import ConfigTemplateSerializer
from netbox.api.fields import RelatedObjectCountField

View File

@ -10,7 +10,7 @@ class DCIMConfig(AppConfig):
def ready(self):
from netbox.models.features import register_models
from utilities.counters import connect_counters
from . import signals, search
from . import signals, search # noqa: F401
from .models import CableTermination, Device, DeviceType, VirtualChassis
# Register models

View File

@ -4,7 +4,7 @@ from django.utils.translation import gettext_lazy as _
from circuits.models import Circuit, CircuitTermination
from dcim.models import *
from utilities.forms.fields import DynamicModelChoiceField, DynamicModelMultipleChoiceField
from utilities.forms.fields import DynamicModelMultipleChoiceField
from .model_forms import CableForm

View File

@ -954,7 +954,7 @@ class PowerOutletTemplateForm(ModularComponentTemplateForm):
queryset=PowerPortTemplate.objects.all(),
required=False,
query_params={
'devicetype_id': '$device_type',
'device_type_id': '$device_type',
}
)
@ -1001,8 +1001,8 @@ class FrontPortTemplateForm(ModularComponentTemplateForm):
queryset=RearPortTemplate.objects.all(),
required=False,
query_params={
'devicetype_id': '$device_type',
'moduletype_id': '$module_type',
'device_type_id': '$device_type',
'module_type_id': '$module_type',
}
)
@ -1063,7 +1063,7 @@ class InventoryItemTemplateForm(ComponentTemplateForm):
queryset=InventoryItemTemplate.objects.all(),
required=False,
query_params={
'devicetype_id': '$device_type'
'device_type_id': '$device_type'
}
)
role = DynamicModelChoiceField(

View File

@ -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',
}
)
)

View File

@ -1,7 +1,6 @@
from typing import Annotated, List, Union
import strawberry
import strawberry_django
__all__ = (
'CabledObjectMixin',
@ -11,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")]]
@ -30,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")]]

View File

@ -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:

View File

@ -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:

View File

@ -4,7 +4,7 @@ from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelatio
from django.core.exceptions import ValidationError
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
from django.db.models import F, Sum
from django.db.models import Sum
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from mptt.models import MPTTModel, TreeForeignKey
@ -22,7 +22,6 @@ from utilities.tracking import TrackingModelMixin
from wireless.choices import *
from wireless.utils import get_channel_attr
__all__ = (
'BaseInterface',
'CabledObjectModel',

View File

@ -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

View File

@ -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)

View File

@ -4856,13 +4856,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]}

View File

@ -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):
"""

View File

@ -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",

View File

@ -1,5 +1,3 @@
import itertools
from django.contrib.contenttypes.models import ContentType
from django.db import transaction

View File

@ -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
)

View File

@ -1,5 +1,3 @@
from rest_framework import serializers
from core.api.serializers_.data import DataFileSerializer, DataSourceSerializer
from extras.models import ConfigTemplate
from netbox.api.serializers import ValidatedModelSerializer

View File

@ -1,5 +1,3 @@
from rest_framework import serializers
from core.models import ObjectType
from extras.models import CustomLink
from netbox.api.fields import ContentTypeField

View File

@ -1,5 +1,3 @@
from rest_framework import serializers
from core.api.serializers_.data import DataFileSerializer, DataSourceSerializer
from core.models import ObjectType
from extras.models import ExportTemplate

View File

@ -1,5 +1,3 @@
from rest_framework import serializers
from core.models import ObjectType
from extras.models import SavedFilter
from netbox.api.fields import ContentTypeField

View File

@ -1,5 +1,3 @@
from rest_framework import serializers
from core.models import ObjectType
from extras.models import Tag
from netbox.api.fields import ContentTypeField, RelatedObjectCountField

View File

@ -1,6 +1,5 @@
from django.http import Http404
from django.shortcuts import get_object_or_404
from django.utils.module_loading import import_string
from django_rq.queues import get_connection
from drf_spectacular.utils import extend_schema, extend_schema_view
from rest_framework import status
@ -15,8 +14,8 @@ from rq import Worker
from core.models import ObjectType
from extras import filtersets
from extras.models import *
from extras.jobs import ScriptJob
from extras.models import *
from netbox.api.authentication import IsAuthenticatedOrLoginNotRequired
from netbox.api.features import SyncedDataMixin
from netbox.api.metadata import ContentTypeMetadata

View File

@ -6,7 +6,7 @@ class ExtrasConfig(AppConfig):
def ready(self):
from netbox.models.features import register_models
from . import dashboard, lookups, search, signals
from . import dashboard, lookups, search, signals # noqa: F401
# Register models
register_models(*self.get_models())

View File

@ -15,7 +15,6 @@ from django.utils.translation import gettext as _
from core.models import ObjectType
from extras.choices import BookmarkOrderingChoices
from netbox.choices import ButtonColorChoices
from utilities.object_types import object_type_identifier, object_type_name
from utilities.permissions import get_permission_for_model
from utilities.querydict import dict_to_querydict

View File

@ -84,7 +84,7 @@ class CustomFieldType(ObjectType):
class CustomFieldChoiceSetType(ObjectType):
choices_for: List[Annotated["CustomFieldType", strawberry.lazy('extras.graphql.types')]]
extra_choices: List[str] | None
extra_choices: List[List[str]] | None
@strawberry_django.type(

View File

@ -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:

View File

@ -1,4 +1,5 @@
from django.db.models import CharField, TextField, Lookup
from django.db.models import CharField, Lookup
from .fields import CachedValueField

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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:

View File

@ -12,7 +12,6 @@ from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Rack, Loca
from extras.choices import *
from extras.models import *
from extras.scripts import BooleanVar, IntegerVar, Script as PythonClass, StringVar
from netbox.events import *
from users.models import Group, User
from utilities.testing import APITestCase, APIViewTestCases
@ -244,9 +243,18 @@ class CustomFieldChoiceSetTest(APIViewTestCases.APIViewTestCase):
@classmethod
def setUpTestData(cls):
choice_sets = (
CustomFieldChoiceSet(name='Choice Set 1', extra_choices=['1A', '1B', '1C', '1D', '1E']),
CustomFieldChoiceSet(name='Choice Set 2', extra_choices=['2A', '2B', '2C', '2D', '2E']),
CustomFieldChoiceSet(name='Choice Set 3', extra_choices=['3A', '3B', '3C', '3D', '3E']),
CustomFieldChoiceSet(
name='Choice Set 1',
extra_choices=[['1A', '1A'], ['1B', '1B'], ['1C', '1C'], ['1D', '1D'], ['1E', '1E']],
),
CustomFieldChoiceSet(
name='Choice Set 2',
extra_choices=[['2A', '2A'], ['2B', '2B'], ['2C', '2C'], ['2D', '2D'], ['2E', '2E']],
),
CustomFieldChoiceSet(
name='Choice Set 3',
extra_choices=[['3A', '3A'], ['3B', '3B'], ['3C', '3C'], ['3D', '3D'], ['3E', '3E']],
),
)
CustomFieldChoiceSet.objects.bulk_create(choice_sets)
@ -784,7 +792,6 @@ class ScriptTest(APITestCase):
super().setUp()
# Monkey-patch the Script model to return our TestScriptClass above
from extras.api.views import ScriptViewSet
Script.python_class = self.python_class
def test_get_script(self):

View File

@ -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')

View File

@ -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',

View File

@ -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',
)

View File

@ -1,4 +1,3 @@
import inspect
import operator
from django.core import validators

View File

@ -6,8 +6,8 @@ from django.db.models import Count, Q
from django.http import HttpResponseBadRequest, HttpResponseForbidden, HttpResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
from django.utils.module_loading import import_string
from django.utils import timezone
from django.utils.module_loading import import_string
from django.utils.translation import gettext as _
from django.views.generic import View
@ -20,7 +20,6 @@ from extras.choices import LogLevelChoices
from extras.dashboard.forms import DashboardWidgetAddForm, DashboardWidgetForm
from extras.dashboard.utils import get_widget_class
from netbox.constants import DEFAULT_ACTION_PERMISSIONS
from netbox.registry import registry
from netbox.views import generic
from netbox.views.generic.mixins import TableMixin
from utilities.forms import ConfirmationForm, get_field_value

View File

@ -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
)

View File

@ -1,5 +1,3 @@
from rest_framework import serializers
from ipam.models import Role
from netbox.api.fields import RelatedObjectCountField
from netbox.api.serializers import NetBoxModelSerializer

View File

@ -1,5 +1,3 @@
from rest_framework import serializers
from dcim.api.serializers_.devices import DeviceSerializer
from ipam.choices import *
from ipam.models import IPAddress, Service, ServiceTemplate

View File

@ -1,5 +1,3 @@
from rest_framework import serializers
from ipam.models import RouteTarget, VRF
from netbox.api.fields import RelatedObjectCountField, SerializedPKRelatedField
from netbox.api.serializers import NetBoxModelSerializer

View File

@ -198,13 +198,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):
"""
@ -262,7 +262,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
)

View File

@ -7,7 +7,7 @@ class IPAMConfig(AppConfig):
def ready(self):
from netbox.models.features import register_models
from . import signals, search
from . import signals, search # noqa: F401
# Register models
register_models(*self.get_models())

View File

@ -1,7 +1,6 @@
from typing import Annotated, List
import strawberry
import strawberry_django
__all__ = (
'IPAddressesMixin',
@ -11,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

View File

@ -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')

View File

@ -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",

View File

@ -1,5 +1,3 @@
from rest_framework import serializers
from extras.models import Tag
from utilities.api import get_related_object_by_attrs
from .base import BaseModelSerializer

View File

@ -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()

View File

@ -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()

View File

@ -388,57 +388,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'])
),
),

View File

@ -3,7 +3,6 @@ from importlib import import_module
from django.apps import apps
from django.conf import settings
from django.conf.urls import include
from django.contrib.admin.views.decorators import staff_member_required
from django.urls import path
from django.utils.module_loading import import_string, module_has_submodule

View File

@ -2,9 +2,7 @@ from collections import OrderedDict
from django.apps import apps
from django.conf import settings
from django.shortcuts import render
from django.urls.exceptions import NoReverseMatch
from django.views.generic import View
from drf_spectacular.utils import extend_schema
from rest_framework import permissions
from rest_framework.response import Response

View File

@ -5,14 +5,12 @@ import os
import platform
import sys
import warnings
from urllib.parse import urlencode, urlsplit
from urllib.parse import urlencode
import django
import requests
from django.contrib.messages import constants as messages
from django.core.exceptions import ImproperlyConfigured, ValidationError
from django.core.validators import URLValidator
from django.utils.encoding import force_str
from django.utils.translation import gettext_lazy as _
from netbox.config import PARAMS as CONFIG_PARAMS
@ -200,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"
)
@ -252,7 +250,7 @@ if STORAGE_BACKEND is not None:
# django-storage-swift
elif STORAGE_BACKEND == 'swift.storage.SwiftStorage':
try:
import swift.utils # type: ignore
import swift.utils # noqa: F401
except ModuleNotFoundError as e:
if getattr(e, 'name') == 'swift':
raise ImproperlyConfigured(

View File

@ -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")

View File

@ -8,7 +8,7 @@ from dcim.models import Site
from utilities.views import register_model_view
from .models import DummyModel
# Trigger registration of custom column
from .tables import mycol
from .tables import mycol # noqa: F401
class DummyModelsView(View):
@ -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')

View File

@ -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(

View File

@ -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']

View File

@ -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
)

View File

@ -6,7 +6,7 @@ class TenancyConfig(AppConfig):
def ready(self):
from netbox.models.features import register_models
from . import search
from . import search # noqa: F401
# Register models
register_models(*self.get_models())

View File

@ -2,7 +2,6 @@ import django_filters
from django.db.models import Q
from django.utils.translation import gettext as _
from extras.filters import TagFilter
from netbox.filtersets import NetBoxModelFilterSet, OrganizationalModelFilterSet
from utilities.filters import ContentTypeFilter, TreeNodeMultipleChoiceFilter
from .models import *

View File

@ -1,8 +1,6 @@
from typing import Annotated, List
import strawberry
import strawberry_django
__all__ = (
'ContactAssignmentsMixin',
@ -12,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

File diff suppressed because it is too large Load Diff

View File

@ -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
)

View File

@ -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()

View File

@ -6,7 +6,7 @@ class UsersConfig(AppConfig):
def ready(self):
from netbox.models.features import register_models
from . import signals
from . import signals # noqa: F401
# Register models
register_models(*self.get_models())

View File

@ -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}<br />' if preference.description else ''
help_text = f'<code>{field_name}</code>'
if preference.description:
help_text = f'{preference.description}<br />{help_text}'

View File

@ -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

View File

@ -1,8 +1,6 @@
from django.test import override_settings
from core.models import ObjectType
from users.models import *
from utilities.testing import ViewTestCases, create_test_user, extract_form_failures
from utilities.testing import ViewTestCases, create_test_user
class UserTestCase(

View File

@ -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'

View File

@ -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)

View File

@ -1,7 +1,6 @@
from collections import defaultdict
from django.core.management.base import BaseCommand
from django.db.models import Count, OuterRef, Subquery
from netbox.registry import registry
from utilities.counters import update_counts

View File

@ -14,7 +14,7 @@ class StrikethroughExtension(markdown.Extension):
"""
def extendMarkdown(self, md):
md.inlinePatterns.register(
markdown.inlinepatterns.SimpleTagPattern(STRIKE_RE, 'del'),
SimpleTagPattern(STRIKE_RE, 'del'),
'strikethrough',
200
)

View File

@ -1,9 +1,9 @@
import datetime
import os
import yaml
from dataclasses import asdict, dataclass, field
from typing import List, Union
from typing import Union
import yaml
from django.core.exceptions import ImproperlyConfigured
from utilities.datetime import datetime_from_timestamp

View File

@ -1,5 +1,5 @@
from django.contrib.postgres.fields import ArrayField
from django.core.serializers.json import Deserializer, Serializer as Serializer_ # noqa
from django.core.serializers.json import Deserializer, Serializer as Serializer_ # noqa: F401
from django.utils.encoding import is_protected_type
# NOTE: Module must contain both Serializer and Deserializer

Some files were not shown because too many files have changed in this diff Show More