chore(ruff): Enable RET rules and add explicit fallbacks

Adopt Ruff `RET` to improve return-flow consistency across the codebase.
Simplify control flow by removing redundant `else` blocks after
`return`, and add explicit `return None` (or equivalent) fallbacks
where appropriate to preserve existing behavior.

Fixes #21411
This commit is contained in:
Martin Hauser
2026-02-18 22:22:47 +01:00
committed by Jeremy Stretch
parent b22e490847
commit ef52ac4203
77 changed files with 249 additions and 249 deletions

View File

@@ -140,7 +140,6 @@ class LoginView(View):
return response
else:
username = form['username'].value()
logger.debug(f"Login form validation failed for username: {remove_linebreaks(username)}")

View File

@@ -185,6 +185,8 @@ class VirtualCircuitTermination(
return self.virtual_circuit.terminations.filter(
role=VirtualCircuitTerminationRoleChoices.ROLE_HUB
)
# Fallback for unexpected roles
return self.virtual_circuit.terminations.none()
def clean(self):
super().clean()

View File

@@ -39,7 +39,7 @@ class ChoiceFieldFix(OpenApiSerializerFieldExtension):
if direction == 'request':
return build_cf
elif direction == "response":
if direction == "response":
value = build_cf
label = {
**build_basic_type(OpenApiTypes.STR),
@@ -53,6 +53,10 @@ class ChoiceFieldFix(OpenApiSerializerFieldExtension):
}
)
# TODO: This function should never implicitly/explicitly return `None`
# The fallback should be well-defined (drf-spectacular expects request/response naming).
return None
def viewset_handles_bulk_create(view):
"""Check if view automatically provides list-based bulk create"""
@@ -75,7 +79,6 @@ class NetBoxAutoSchema(AutoSchema):
def is_bulk_action(self):
if hasattr(self.view, "action") and self.view.action in BULK_ACTIONS:
return True
else:
return False
def get_operation_id(self):
@@ -316,7 +319,6 @@ class FixSerializedPKRelatedField(OpenApiSerializerFieldExtension):
if direction == "response":
component = auto_schema.resolve_serializer(self.target.serializer, direction)
return component.ref if component else None
else:
return build_basic_type(OpenApiTypes.INT)

View File

@@ -34,14 +34,14 @@ class ObjectTypeSerializer(BaseModelSerializer):
@extend_schema_field(OpenApiTypes.STR)
def get_rest_api_endpoint(self, obj):
if not (model := obj.model_class()):
return
return None
try:
return get_action_url(model, action='list', rest_api=True)
except NoReverseMatch:
return
return None
@extend_schema_field(OpenApiTypes.STR)
def get_description(self, obj):
if not (model := obj.model_class()):
return
return None
return inspect.getdoc(model)

View File

@@ -285,5 +285,4 @@ class BackgroundTaskViewSet(BaseRQViewSet):
stopped_jobs = stop_rq_job(id)
if len(stopped_jobs) == 1:
return HttpResponse(status=200)
else:
return HttpResponse(status=204)

View File

@@ -144,7 +144,7 @@ class Command(BaseCommand):
# If Python code has been passed, execute it and exit.
if options['command']:
exec(options['command'], namespace)
return
return None
# Try to enable tab-complete
try:

View File

@@ -98,6 +98,7 @@ class DataSource(JobsMixin, PrimaryModel):
def get_type_display(self):
if backend := registry['data_backends'].get(self.type):
return backend.label
return None
def get_status_color(self):
return DataSourceStatusChoices.colors.get(self.status)

View File

@@ -79,7 +79,6 @@ class ManagedFile(SyncedDataMixin, models.Model):
'scripts': settings.SCRIPTS_ROOT,
'reports': settings.REPORTS_ROOT,
}[self.file_root]
else:
return ""
def sync_data(self):

View File

@@ -146,7 +146,7 @@ class Job(models.Model):
if self.object_type:
if self.object_type.model == 'reportmodule':
return reverse('extras:report_result', kwargs={'job_pk': self.pk})
elif self.object_type.model == 'scriptmodule':
if self.object_type.model == 'scriptmodule':
return reverse('extras:script_result', kwargs={'job_pk': self.pk})
return reverse('core:job', args=[self.pk])

View File

@@ -218,19 +218,22 @@ class ObjectType(ContentType):
def app_verbose_name(self):
if model := self.model_class():
return model._meta.app_config.verbose_name
return None
@property
def model_verbose_name(self):
if model := self.model_class():
return model._meta.verbose_name
return None
@property
def model_verbose_name_plural(self):
if model := self.model_class():
return model._meta.verbose_name_plural
return None
@property
def is_plugin_model(self):
if not (model := self.model_class()):
return # Return null if model class is invalid
return None # Return null if model class is invalid
return isinstance(model._meta.app_config, PluginConfig)

View File

@@ -23,6 +23,7 @@ class ConnectedEndpointsSerializer(serializers.ModelSerializer):
def get_connected_endpoints_type(self, obj):
if endpoints := obj.connected_endpoints:
return f'{endpoints[0]._meta.app_label}.{endpoints[0]._meta.model_name}'
return None
@extend_schema_field(serializers.ListField(allow_null=True))
def get_connected_endpoints(self, obj):
@@ -33,6 +34,7 @@ class ConnectedEndpointsSerializer(serializers.ModelSerializer):
serializer = get_serializer_for_model(endpoints[0])
context = {'request': self.context['request']}
return serializer(endpoints, nested=True, many=True, context=context).data
return None
@extend_schema_field(serializers.BooleanField)
def get_connected_endpoints_reachable(self, obj):

View File

@@ -222,7 +222,6 @@ class RackViewSet(NetBoxModelViewSet):
)
return HttpResponse(drawing.tostring(), content_type='image/svg+xml')
else:
# Return a JSON representation of the rack units in the elevation
elevation = rack.get_rack_units(
face=data['face'],
@@ -241,6 +240,9 @@ class RackViewSet(NetBoxModelViewSet):
rack_units = serializers.RackUnitSerializer(page, many=True, context={'request': request})
return self.get_paginated_response(rack_units.data)
# TODO: This endpoint should always return an HttpResponse/DRF Response; `None` is not a meaningful result.
return None
#
# Rack reservations

View File

@@ -704,13 +704,11 @@ class DeviceTypeFilterSet(PrimaryModelFilterSet):
def _has_front_image(self, queryset, name, value):
if value:
return queryset.exclude(front_image='')
else:
return queryset.filter(front_image='')
def _has_rear_image(self, queryset, name, value):
if value:
return queryset.exclude(rear_image='')
else:
return queryset.filter(rear_image='')
def _console_ports(self, queryset, name, value):
@@ -1855,7 +1853,6 @@ class CabledObjectFilterSet(django_filters.FilterSet):
def filter_occupied(self, queryset, name, value):
if value:
return queryset.filter(Q(cable__isnull=False) | Q(mark_connected=True))
else:
return queryset.filter(cable__isnull=True, mark_connected=False)
@@ -1867,7 +1864,6 @@ class PathEndpointFilterSet(django_filters.FilterSet):
def filter_connected(self, queryset, name, value):
if value:
return queryset.filter(_path__is_active=True)
else:
return queryset.filter(Q(_path__isnull=True) | Q(_path__is_active=False))
@@ -2045,7 +2041,6 @@ class MACAddressFilterSet(PrimaryModelFilterSet):
}
if value:
return queryset.exclude(**params)
else:
return queryset.filter(**params)
def filter_primary(self, queryset, name, value):
@@ -2058,7 +2053,6 @@ class MACAddressFilterSet(PrimaryModelFilterSet):
query = Q(pk__in=interface_mac_ids) | Q(pk__in=vminterface_mac_ids)
if value:
return queryset.filter(query)
else:
return queryset.exclude(query)
@@ -2302,7 +2296,6 @@ class InterfaceFilterSet(
Q(wireless_link__isnull=False) |
Q(mark_connected=True)
)
else:
return queryset.filter(
cable__isnull=True,
wireless_link__isnull=True,
@@ -2677,7 +2670,6 @@ class CableFilterSet(TenancyFilterSet, PrimaryModelFilterSet):
.values("id")
)
return queryset.exclude(id__in=terminated_ids)
else:
return queryset.filter(terminations__cable_end=CableEndChoices.SIDE_A).filter(
terminations__cable_end=CableEndChoices.SIDE_B
)

View File

@@ -1598,7 +1598,7 @@ class InterfaceBulkEditForm(
if not self.cleaned_data['mode']:
if self.cleaned_data['untagged_vlan']:
raise forms.ValidationError({'untagged_vlan': _("Interface mode must be specified to assign VLANs")})
elif self.cleaned_data['tagged_vlans']:
if self.cleaned_data['tagged_vlans']:
raise forms.ValidationError({'tagged_vlans': _("Interface mode must be specified to assign VLANs")})
# Untagged interfaces cannot be assigned tagged VLANs

View File

@@ -796,7 +796,6 @@ class ModuleImportForm(ModuleCommonForm, PrimaryModelImportForm):
# Make sure replicate_components is True when it's not included in the uploaded data
if 'replicate_components' not in self.data:
return True
else:
return self.cleaned_data['replicate_components']
@@ -1079,7 +1078,6 @@ class InterfaceImportForm(OwnerCSVMixin, NetBoxModelImportForm):
# Make sure enabled is True when it's not included in the uploaded data
if 'enabled' not in self.data:
return True
else:
return self.cleaned_data['enabled']
def clean_vdcs(self):

View File

@@ -1360,7 +1360,7 @@ class InventoryItemTemplateForm(ComponentTemplateForm):
]
if len(selected_objects) > 1:
raise forms.ValidationError(_("An InventoryItem can only be assigned to a single component."))
elif selected_objects:
if selected_objects:
self.instance.component = self.cleaned_data[selected_objects[0]]
else:
self.instance.component = None
@@ -1846,7 +1846,7 @@ class InventoryItemForm(DeviceComponentForm):
]
if len(selected_objects) > 1:
raise forms.ValidationError(_("An InventoryItem can only be assigned to a single component."))
elif selected_objects:
if selected_objects:
self.instance.component = self.cleaned_data[selected_objects[0]]
else:
self.instance.component = None
@@ -1981,7 +1981,7 @@ class MACAddressForm(PrimaryModelForm):
raise forms.ValidationError({
selected_objects[1]: _("A MAC address can only be assigned to a single object.")
})
elif selected_objects:
if selected_objects:
self.instance.assigned_object = self.cleaned_data[selected_objects[0]]
else:
self.instance.assigned_object = None

View File

@@ -486,7 +486,6 @@ class MACAddressFilter(PrimaryModelFilter):
query = Q(**{f'{prefix}pk__in': interface_mac_ids}) | Q(**{f'{prefix}pk__in': vminterface_mac_ids})
if value:
return Q(query)
else:
return ~Q(query)
@@ -571,7 +570,6 @@ class InterfaceFilter(
def connected(self, queryset, value: bool, prefix: str):
if value is True:
return queryset, Q(**{f"{prefix}_path__is_active": True})
else:
return queryset, Q(**{f"{prefix}_path__isnull": True}) | Q(**{f"{prefix}_path__is_active": False})
@strawberry_django.filter_field
@@ -583,10 +581,11 @@ class InterfaceFilter(
):
if value == InterfaceKindEnum.KIND_PHYSICAL:
return queryset, ~Q(**{f"{prefix}type__in": NONCONNECTABLE_IFACE_TYPES})
elif value == InterfaceKindEnum.KIND_VIRTUAL:
if value == InterfaceKindEnum.KIND_VIRTUAL:
return queryset, Q(**{f"{prefix}type__in": VIRTUAL_IFACE_TYPES})
elif value == InterfaceKindEnum.KIND_WIRELESS:
if value == InterfaceKindEnum.KIND_WIRELESS:
return queryset, Q(**{f"{prefix}type__in": WIRELESS_IFACE_TYPES})
return queryset, Q()
@strawberry_django.filter_type(models.InterfaceTemplate, lookups=True)

View File

@@ -66,6 +66,7 @@ class InventoryItemTemplateComponentType:
return PowerPortTemplateType
if type(instance) is RearPortTemplate:
return RearPortTemplateType
return None
class InventoryItemComponentType:
@@ -96,6 +97,7 @@ class InventoryItemComponentType:
return PowerPortType
if type(instance) is RearPort:
return RearPortType
return None
class ConnectedEndpointType:
@@ -135,3 +137,4 @@ class ConnectedEndpointType:
return ProviderNetworkType
if type(instance) is RearPort:
return RearPortType
return None

View File

@@ -673,12 +673,14 @@ class CablePath(models.Model):
if self.path:
ct_id, _ = decompile_path_node(self.path[0][0])
return ContentType.objects.get_for_id(ct_id)
return None
@property
def destination_type(self):
if self.is_complete:
ct_id, _ = decompile_path_node(self.path[-1][0])
return ContentType.objects.get_for_id(ct_id)
return None
@property
def _path_decompiled(self):
@@ -921,7 +923,7 @@ class CablePath(models.Model):
if not circuit_terminations.exists():
break
elif all([ct._provider_network for ct in circuit_terminations]):
if all([ct._provider_network for ct in circuit_terminations]):
# Circuit terminates to a ProviderNetwork
path.extend([
[object_to_path_node(ct) for ct in circuit_terminations],
@@ -929,14 +931,14 @@ class CablePath(models.Model):
])
is_complete = True
break
elif all([ct.termination and not ct.cable for ct in circuit_terminations]):
if all([ct.termination and not ct.cable for ct in circuit_terminations]):
# Circuit terminates to a Region/Site/etc.
path.extend([
[object_to_path_node(ct) for ct in circuit_terminations],
[object_to_path_node(ct.termination) for ct in circuit_terminations],
])
break
elif any([ct.cable in links for ct in circuit_terminations]):
if any([ct.cable in links for ct in circuit_terminations]):
# No valid path
is_split = True
break

View File

@@ -731,6 +731,7 @@ class BaseInterface(models.Model):
def mac_address(self):
if self.primary_mac_address:
return self.primary_mac_address.mac_address
return None
class Interface(
@@ -943,7 +944,7 @@ class Interface(
"The selected parent interface ({interface}) belongs to a different device ({device})"
).format(interface=self.parent, device=self.parent.device)
})
elif self.parent.device.virtual_chassis != self.device.virtual_chassis:
if self.parent.device.virtual_chassis != self.device.virtual_chassis:
raise ValidationError({
'parent': _(
"The selected parent interface ({interface}) belongs to {device}, which is not part of "
@@ -965,7 +966,7 @@ class Interface(
"The selected bridge interface ({bridge}) belongs to a different device ({device})."
).format(bridge=self.bridge, device=self.bridge.device)
})
elif self.bridge.device.virtual_chassis != self.device.virtual_chassis:
if self.bridge.device.virtual_chassis != self.device.virtual_chassis:
raise ValidationError({
'bridge': _(
"The selected bridge interface ({interface}) belongs to {device}, which is not part of virtual "
@@ -993,7 +994,7 @@ class Interface(
"The selected LAG interface ({lag}) belongs to a different device ({device})."
).format(lag=self.lag, device=self.lag.device)
})
elif self.lag.device.virtual_chassis != self.device.virtual_chassis:
if self.lag.device.virtual_chassis != self.device.virtual_chassis:
raise ValidationError({
'lag': _(
"The selected LAG interface ({lag}) belongs to {device}, which is not part of virtual chassis "
@@ -1085,7 +1086,6 @@ class Interface(
# Return the opposite side of the attached wireless link
if self.wireless_link.interface_a == self:
return [self.wireless_link.interface_b]
else:
return [self.wireless_link.interface_a]
return []

View File

@@ -762,11 +762,11 @@ class Device(
def __str__(self):
if self.label and self.asset_tag:
return f'{self.label} ({self.asset_tag})'
elif self.label:
if self.label:
return self.label
elif self.device_type and self.asset_tag:
if self.device_type and self.asset_tag:
return f'{self.device_type.manufacturer} {self.device_type.model} ({self.asset_tag})'
elif self.device_type:
if self.device_type:
return f'{self.device_type.manufacturer} {self.device_type.model} ({self.pk})'
return super().__str__()
@@ -1046,6 +1046,7 @@ class Device(
return self.name
if self.virtual_chassis:
return f'{self.virtual_chassis.name}:{self.vc_position}'
return None
@property
def identifier(self):
@@ -1058,11 +1059,10 @@ class Device(
def primary_ip(self):
if ConfigItem('PREFER_IPV4')() and self.primary_ip4:
return self.primary_ip4
elif self.primary_ip6:
if self.primary_ip6:
return self.primary_ip6
elif self.primary_ip4:
if self.primary_ip4:
return self.primary_ip4
else:
return None
@property
@@ -1278,11 +1278,10 @@ class VirtualDeviceContext(PrimaryModel):
def primary_ip(self):
if ConfigItem('PREFER_IPV4')() and self.primary_ip4:
return self.primary_ip4
elif self.primary_ip6:
if self.primary_ip6:
return self.primary_ip6
elif self.primary_ip4:
if self.primary_ip4:
return self.primary_ip4
else:
return None
def clean(self):
@@ -1369,7 +1368,7 @@ class MACAddress(PrimaryModel):
raise ValidationError(
_("Cannot unassign MAC Address while it is designated as the primary MAC for an object")
)
elif original_assigned_object != assigned_object:
if original_assigned_object != assigned_object:
raise ValidationError(
_("Cannot reassign MAC Address while it is designated as the primary MAC for an object")
)

View File

@@ -35,6 +35,7 @@ class RenderConfigMixin(models.Model):
return self.role.config_template
if self.platform and self.platform.config_template:
return self.platform.config_template
return None
class CachedScopeMixin(models.Model):

View File

@@ -190,9 +190,8 @@ class CableTraceSVG:
if hasattr(instance, 'role'):
# Device
return instance.role.color
elif instance._meta.model_name == 'circuit' and instance.type.color:
if instance._meta.model_name == 'circuit' and instance.type.color:
return instance.type.color
else:
# Other parent object
return 'e0e0e0'

View File

@@ -32,7 +32,6 @@ class ScriptSerializer(ValidatedModelSerializer):
return {
k: v.__class__.__name__ for k, v in obj.python_class()._get_vars().items()
}
else:
return {}
@extend_schema_field(serializers.CharField())
@@ -43,7 +42,6 @@ class ScriptSerializer(ValidatedModelSerializer):
def get_description(self, obj):
if obj.python_class:
return obj.python_class().description
else:
return None

View File

@@ -278,7 +278,7 @@ class ObjectListWidget(DashboardWidget):
model = ObjectType.objects.get_by_natural_key(app_label, model_name).model_class()
if not model:
logger.debug(f"Dashboard Widget model_class not found: {app_label}:{model_name}")
return
return None
# Evaluate user's permission. Note that this controls only whether the HTMX element is
# embedded on the page: The view itself will also evaluate permissions separately.

View File

@@ -117,6 +117,7 @@ class CustomFieldChoiceSetImportForm(OwnerCSVMixin, CSVModelForm):
value, label = line, line
data.append((value, label))
return data
return None
class CustomLinkImportForm(OwnerCSVMixin, CSVModelForm):

View File

@@ -390,6 +390,7 @@ class TableConfigForm(forms.ModelForm):
return columns.split(',') if type(columns) is str else columns
if self.instance is not None:
return self.instance.columns
return None
class BookmarkForm(forms.ModelForm):

View File

@@ -45,7 +45,6 @@ class Empty(Lookup):
sql, params = compiler.compile(self.lhs)
if self.rhs:
return f"CAST(LENGTH({sql}) AS BOOLEAN) IS NOT TRUE", params
else:
return f"CAST(LENGTH({sql}) AS BOOLEAN) IS TRUE", params

View File

@@ -58,7 +58,6 @@ class PythonModuleMixin:
if name == '__init__':
# File is a package
return os.path.basename(path)
else:
return name
def get_module(self):

View File

@@ -291,7 +291,6 @@ class Webhook(CustomFieldsMixin, ExportTemplatesMixin, TagsMixin, OwnerMixin, Ch
"""
if self.body_template:
return render_jinja2(self.body_template, context)
else:
return json.dumps(context, cls=JSONEncoder)
def render_payload_url(self, context):

View File

@@ -1470,9 +1470,8 @@ class BaseScriptView(generic.ObjectView):
def get_object(self, **kwargs):
if pk := kwargs.get('pk', False):
return get_object_or_404(self.queryset, pk=pk)
elif (module := kwargs.get('module')) and (name := kwargs.get('name', False)):
if (module := kwargs.get('module')) and (name := kwargs.get('name', False)):
return get_object_or_404(self.queryset, module__file_path=f'{module}.py', name=name)
else:
raise Http404
def _get_script_class(self, script):
@@ -1481,6 +1480,7 @@ class BaseScriptView(generic.ObjectView):
"""
if script_class := script.python_class:
return script_class()
return None
class ScriptView(BaseScriptView):
@@ -1674,7 +1674,7 @@ class ScriptResultView(TableMixin, generic.ObjectView):
response['Content-Disposition'] = f'attachment; filename="{filename}"'
return response
elif job.completed:
if job.completed:
table = self.get_table(job, request, bulk_actions=False)
log_threshold = request.GET.get('log_threshold', LogLevelChoices.LOG_INFO)

View File

@@ -124,7 +124,6 @@ def send_webhook(event_rule, object_type, event_type, data, timestamp, username,
if 200 <= response.status_code <= 299:
logger.info(f"Request succeeded; response status {response.status_code}")
return f"Status {response.status_code} returned, webhook successfully processed."
else:
logger.warning(f"Request failed; response status {response.status_code}: {response.content}")
raise requests.exceptions.RequestException(
f"Status {response.status_code} returned with content '{response.content}', webhook FAILED to process."

View File

@@ -95,7 +95,7 @@ class PrefixLengthSerializer(serializers.Serializer):
raise serializers.ValidationError({
'prefix_length': 'Invalid prefix length ({}) for IPv4'.format(requested_prefix)
})
elif prefix.family == 6 and requested_prefix > 128:
if prefix.family == 6 and requested_prefix > 128:
raise serializers.ValidationError({
'prefix_length': 'Invalid prefix length ({}) for IPv6'.format(requested_prefix)
})
@@ -174,11 +174,11 @@ class AvailableIPRequestSerializer(serializers.Serializer):
parent.mask_length
)
})
elif parent.family == 4 and prefix_length > 32:
if parent.family == 4 and prefix_length > 32:
raise serializers.ValidationError({
'prefix_length': 'Invalid prefix length ({}) for IPv6'.format(prefix_length)
})
elif parent.family == 6 and prefix_length > 128:
if parent.family == 6 and prefix_length > 128:
raise serializers.ValidationError({
'prefix_length': 'Invalid prefix length ({}) for IPv4'.format(prefix_length)
})

View File

@@ -473,7 +473,6 @@ class PrefixFilterSet(PrimaryModelFilterSet, ScopedFilterSet, TenancyFilterSet,
if '/' in value:
return queryset.filter(prefix__net_contains_or_equals=str(netaddr.IPNetwork(value).cidr))
# Searching by IP address
else:
return queryset.filter(prefix__net_contains=str(netaddr.IPAddress(value)))
except (AddrFormatError, ValueError):
return queryset.none()
@@ -809,7 +808,6 @@ class IPAddressFilterSet(PrimaryModelFilterSet, TenancyFilterSet, ContactModelFi
assigned_object_type__in=content_types,
assigned_object_id__isnull=False
)
else:
return queryset.exclude(
assigned_object_type__in=content_types,
assigned_object_id__isnull=False
@@ -821,7 +819,6 @@ class IPAddressFilterSet(PrimaryModelFilterSet, TenancyFilterSet, ContactModelFi
assigned_object_type__isnull=True,
assigned_object_id__isnull=True
)
else:
return queryset.filter(
assigned_object_type__isnull=True,
assigned_object_id__isnull=True

View File

@@ -372,14 +372,12 @@ class IPAddressImportForm(PrimaryModelImportForm):
# Make sure is_primary is None when it's not included in the uploaded data
if 'is_primary' not in self.data:
return None
else:
return self.cleaned_data['is_primary']
def clean_is_oob(self):
# Make sure is_oob is None when it's not included in the uploaded data
if 'is_oob' not in self.data:
return None
else:
return self.cleaned_data['is_oob']
def clean(self):

View File

@@ -392,7 +392,7 @@ class IPAddressForm(TenancyForm, PrimaryModelForm):
raise forms.ValidationError({
selected_objects[1]: _("An IP address can only be assigned to a single object.")
})
elif selected_objects:
if selected_objects:
assigned_object = self.cleaned_data[selected_objects[0]]
if self.instance.pk and self.instance.assigned_object and assigned_object != self.instance.assigned_object:
if self.cleaned_data['primary_for_parent']:

View File

@@ -122,7 +122,7 @@ class NetIn(Lookup):
if with_mask and not without_mask:
return address_in_clause, with_mask
elif not with_mask and without_mask:
if not with_mask and without_mask:
return host_in_clause, without_mask
in_clause = '({}) OR ({})'.format(address_in_clause, host_in_clause)

View File

@@ -167,6 +167,7 @@ class Aggregate(ContactsMixin, GetAvailablePrefixesMixin, PrimaryModel):
def ipv6_full(self):
if self.prefix and self.prefix.version == 6:
return netaddr.IPAddress(self.prefix).format(netaddr.ipv6_full)
return None
def get_child_prefixes(self):
"""
@@ -344,6 +345,7 @@ class Prefix(ContactsMixin, GetAvailablePrefixesMixin, CachedScopeMixin, Primary
def ipv6_full(self):
if self.prefix and self.prefix.version == 6:
return netaddr.IPAddress(self.prefix).format(netaddr.ipv6_full)
return None
@property
def depth(self):
@@ -395,7 +397,6 @@ class Prefix(ContactsMixin, GetAvailablePrefixesMixin, CachedScopeMixin, Primary
"""
if self.vrf is None and self.status == PrefixStatusChoices.STATUS_CONTAINER:
return Prefix.objects.filter(prefix__net_contained=str(self.prefix))
else:
return Prefix.objects.filter(prefix__net_contained=str(self.prefix), vrf=self.vrf)
def get_child_ranges(self, **kwargs):
@@ -416,7 +417,6 @@ class Prefix(ContactsMixin, GetAvailablePrefixesMixin, CachedScopeMixin, Primary
"""
if self.vrf is None and self.status == PrefixStatusChoices.STATUS_CONTAINER:
return IPAddress.objects.filter(address__net_host_contained=str(self.prefix))
else:
return IPAddress.objects.filter(address__net_host_contained=str(self.prefix), vrf=self.vrf)
def get_available_ips(self):
@@ -827,6 +827,7 @@ class IPAddress(ContactsMixin, PrimaryModel):
def ipv6_full(self):
if self.address and self.address.version == 6:
return netaddr.IPAddress(self.address).format(netaddr.ipv6_full)
return None
def get_duplicates(self):
return IPAddress.objects.filter(
@@ -852,6 +853,7 @@ class IPAddress(ContactsMixin, PrimaryModel):
])
if available_ips:
return next(iter(available_ips))
return None
def get_related_ips(self):
"""

View File

@@ -923,7 +923,7 @@ class IPAddressEditView(generic.ObjectEditView):
def get_extra_addanother_params(self, request):
if 'interface' in request.GET:
return {'interface': request.GET['interface']}
elif 'vminterface' in request.GET:
if 'vminterface' in request.GET:
return {'vminterface': request.GET['vminterface']}
return {}

View File

@@ -25,10 +25,10 @@ class TokenAuthentication(BaseAuthentication):
def authenticate(self, request):
# Authorization header is not present; ignore
if not (auth := get_authorization_header(request).split()):
return
return None
# Unrecognized header; ignore
if auth[0].lower() not in (V1_KEYWORD.lower().encode(), V2_KEYWORD.lower().encode()):
return
return None
# Check for extraneous token content
if len(auth) != 2:
raise exceptions.AuthenticationFailed(
@@ -150,6 +150,7 @@ class TokenPermissions(DjangoObjectPermissions):
# If token authentication is in use, verify that the token allows write operations (for unsafe methods).
if request.method in SAFE_METHODS or request.auth.write_enabled:
return True
return False
def has_permission(self, request, view):

View File

@@ -47,7 +47,6 @@ class ChoiceField(serializers.Field):
if data is None:
if self.allow_null:
return True, None
else:
data = ''
return super().validate_empty_values(data)
@@ -59,6 +58,7 @@ class ChoiceField(serializers.Field):
'value': obj,
'label': self._choices.get(obj, ''),
}
return None
def to_internal_value(self, data):
if data == '':

View File

@@ -40,7 +40,6 @@ class OptionalLimitOffsetPagination(LimitOffsetPagination):
if self.limit:
return list(queryset[self.offset:self.offset + self.limit])
else:
return list(queryset[self.offset:])
def get_limit(self, request):

View File

@@ -211,7 +211,7 @@ class RemoteUserBackend(_RemoteUserBackend):
logger.debug(
f"trying to authenticate {remote_user} with groups {remote_groups}")
if not remote_user:
return
return None
user = None
username = self.clean_username(remote_user)
@@ -235,7 +235,6 @@ class RemoteUserBackend(_RemoteUserBackend):
return self.configure_groups(user, remote_groups)
else:
return user
else:
return None
def _is_superuser(self, user):

View File

@@ -138,13 +138,13 @@ class BaseFilterSet(django_filters.FilterSet):
)):
return FILTER_NUMERIC_BASED_LOOKUP_MAP
elif isinstance(existing_filter, (
if isinstance(existing_filter, (
filters.TreeNodeMultipleChoiceFilter,
)):
# TreeNodeMultipleChoiceFilter only support negation but must maintain the `in` lookup expression
return FILTER_TREENODE_NEGATION_LOOKUP_MAP
elif isinstance(existing_filter, (
if isinstance(existing_filter, (
django_filters.ModelChoiceFilter,
django_filters.ModelMultipleChoiceFilter,
TagFilter
@@ -152,7 +152,7 @@ class BaseFilterSet(django_filters.FilterSet):
# These filter types support only negation
return FILTER_NEGATION_LOOKUP_MAP
elif isinstance(existing_filter, (
if isinstance(existing_filter, (
django_filters.filters.CharFilter,
django_filters.ChoiceFilter,
django_filters.MultipleChoiceFilter,
@@ -387,7 +387,7 @@ class AttributeFiltersMixin:
def _get_field_lookup(self, key):
if not key.startswith(self.attribute_filter_prefix):
return
return None
lookup = key.split(self.attribute_filter_prefix, 1)[1] # Strip prefix
return f'{self.attributes_field_name}__{lookup}'

View File

@@ -186,11 +186,10 @@ class TreeNodeFilter:
# Handle different relationship types
if isinstance(model_field, (ManyToManyField, ManyToManyRel)):
return queryset, Q(**{f'{model_field_name}__in': related_model.objects.filter(q_filter)})
elif isinstance(model_field, ForeignKey):
if isinstance(model_field, ForeignKey):
return queryset, Q(**{f'{model_field_name}__{k}': v for k, v in q_filter.children})
elif isinstance(model_field, ManyToOneRel):
if isinstance(model_field, ManyToOneRel):
return queryset, Q(**{f'{model_field_name}__in': related_model.objects.filter(q_filter)})
else:
return queryset, Q(**{f'{model_field_name}__{k}': v for k, v in q_filter.children})
@@ -205,17 +204,17 @@ def generate_tree_node_q_filter(model_class, filter_value: TreeNodeFilter) -> Q:
if filter_value.match_type == TreeNodeMatch.EXACT:
return Q(id=filter_value.id)
elif filter_value.match_type == TreeNodeMatch.DESCENDANTS:
if filter_value.match_type == TreeNodeMatch.DESCENDANTS:
return Q(tree_id=node.tree_id, lft__gt=node.lft, rght__lt=node.rght)
elif filter_value.match_type == TreeNodeMatch.SELF_AND_DESCENDANTS:
if filter_value.match_type == TreeNodeMatch.SELF_AND_DESCENDANTS:
return Q(tree_id=node.tree_id, lft__gte=node.lft, rght__lte=node.rght)
elif filter_value.match_type == TreeNodeMatch.CHILDREN:
if filter_value.match_type == TreeNodeMatch.CHILDREN:
return Q(tree_id=node.tree_id, level=node.level + 1, lft__gt=node.lft, rght__lt=node.rght)
elif filter_value.match_type == TreeNodeMatch.SIBLINGS:
if filter_value.match_type == TreeNodeMatch.SIBLINGS:
return Q(tree_id=node.tree_id, level=node.level, parent=node.parent) & ~Q(id=node.id)
elif filter_value.match_type == TreeNodeMatch.ANCESTORS:
if filter_value.match_type == TreeNodeMatch.ANCESTORS:
return Q(tree_id=node.tree_id, lft__lt=node.lft, rght__gt=node.rght)
elif filter_value.match_type == TreeNodeMatch.PARENT:
if filter_value.match_type == TreeNodeMatch.PARENT:
return Q(id=node.parent_id) if node.parent_id else Q(pk__in=[])
return Q()

View File

@@ -34,7 +34,6 @@ class BaseObjectType:
# Enforce object permissions on the queryset
if hasattr(queryset, 'restrict'):
return queryset.restrict(info.context.request.user, 'view')
else:
return queryset
@strawberry_django.field

View File

@@ -37,7 +37,6 @@ class NetBoxGraphQLView(GraphQLView):
if settings.LOGIN_REQUIRED and not request.user.is_authenticated:
if request.accepts("text/html"):
return redirect_to_login(reverse('graphql'))
else:
return HttpResponseForbidden("No credentials provided.")
return super().dispatch(request, *args, **kwargs)

View File

@@ -71,7 +71,7 @@ class CoreMiddleware:
"""
# Don't catch exceptions when in debug mode
if settings.DEBUG:
return
return None
# Cleanly handle exceptions that occur from REST API requests
if is_api_request(request):
@@ -79,7 +79,7 @@ class CoreMiddleware:
# Ignore Http404s (defer to Django's built-in 404 handling)
if isinstance(exception, Http404):
return
return None
# Determine the type of exception. If it's a common issue, return a custom error page with instructions.
custom_template = None
@@ -93,6 +93,7 @@ class CoreMiddleware:
# Return a custom error message, or fall back to Django's default 500 error handling
if custom_template:
return handler_500(request, template_name=custom_template)
return None
class RemoteUserMiddleware(RemoteUserMiddleware_):
@@ -139,7 +140,6 @@ class RemoteUserMiddleware(RemoteUserMiddleware_):
if request.user.is_authenticated:
if request.user.get_username() == self.clean_username(username, request):
return self.get_response(request)
else:
# An authenticated user is associated with the request, but
# it does not match the authorized user in the header.
self._remove_invalid_user(request)
@@ -250,3 +250,4 @@ class MaintenanceModeMiddleware:
messages.error(request, error_message)
return HttpResponseRedirect(request.path_info)
return None

View File

@@ -246,7 +246,7 @@ class CustomFieldsMixin(models.Model):
# Skip hidden fields if 'omit_hidden' is True
if omit_hidden and field.ui_visible == CustomFieldUIVisibleChoices.HIDDEN:
continue
elif omit_hidden and field.ui_visible == CustomFieldUIVisibleChoices.IF_SET and not value:
if omit_hidden and field.ui_visible == CustomFieldUIVisibleChoices.IF_SET and not value:
continue
data[field] = field.deserialize(value)
@@ -611,6 +611,7 @@ class SyncedDataMixin(models.Model):
return DataFile.objects.get(source=self.data_source, path=self.data_path)
except DataFile.DoesNotExist:
pass
return None
def sync(self, save=False):
"""

View File

@@ -49,7 +49,7 @@ class ObjectAction:
try:
return get_action_url(obj, action=cls.name, kwargs=kwargs)
except NoReverseMatch:
return
return None
@classmethod
def get_url_params(cls, context):

View File

@@ -84,6 +84,7 @@ class SearchIndex:
"""
if value := getattr(instance, field_name):
return str(value)
return None
@classmethod
def get_category(cls):

View File

@@ -248,7 +248,7 @@ class CachedValueSearchBackend(SearchBackend):
try:
get_indexer(instance)
except KeyError:
return
return None
ct = ContentType.objects.get_for_model(instance)
qs = CachedValue.objects.filter(object_type=ct, object_id=instance.pk)

View File

@@ -61,15 +61,18 @@ class DateColumn(tables.Column):
def render(self, value):
if value:
return value.isoformat()
return None
def value(self, value):
if value:
return value.isoformat()
return None
@classmethod
def from_field(cls, field, **kwargs):
if isinstance(field, DateField):
return cls(**kwargs)
return None
@library.register
@@ -89,15 +92,18 @@ class DateTimeColumn(tables.Column):
current_tz = zoneinfo.ZoneInfo(settings.TIME_ZONE)
value = value.astimezone(current_tz)
return f"{value.date().isoformat()} {value.time().isoformat(timespec=self.timespec)}"
return None
def value(self, value):
if value:
return value.isoformat()
return None
@classmethod
def from_field(cls, field, **kwargs):
if isinstance(field, DateTimeField):
return cls(**kwargs)
return None
class DurationColumn(tables.Column):

View File

@@ -70,7 +70,7 @@ class FormClassesTestCase(TestCase):
Return the base form class for creating/editing the given model.
"""
if model._meta.app_label == 'dummy_plugin':
return
return None
if issubclass(model, PrimaryModel):
return PrimaryModelForm
if issubclass(model, OrganizationalModel):
@@ -79,6 +79,7 @@ class FormClassesTestCase(TestCase):
return NestedGroupModelForm
if issubclass(model, NetBoxModel):
return NetBoxModelForm
return None
@staticmethod
def get_bulk_edit_form_base_class(model):
@@ -86,7 +87,7 @@ class FormClassesTestCase(TestCase):
Return the base form class for bulk editing the given model.
"""
if model._meta.app_label == 'dummy_plugin':
return
return None
if issubclass(model, PrimaryModel):
return PrimaryModelBulkEditForm
if issubclass(model, OrganizationalModel):
@@ -95,6 +96,7 @@ class FormClassesTestCase(TestCase):
return NestedGroupModelBulkEditForm
if issubclass(model, NetBoxModel):
return NetBoxModelBulkEditForm
return None
@staticmethod
def get_import_form_base_class(model):
@@ -102,7 +104,7 @@ class FormClassesTestCase(TestCase):
Return the base form class for importing the given model.
"""
if model._meta.app_label == 'dummy_plugin':
return
return None
if issubclass(model, PrimaryModel):
return PrimaryModelImportForm
if issubclass(model, OrganizationalModel):
@@ -111,6 +113,7 @@ class FormClassesTestCase(TestCase):
return NestedGroupModelImportForm
if issubclass(model, NetBoxModel):
return NetBoxModelImportForm
return None
@staticmethod
def get_filterset_form_base_class(model):
@@ -118,7 +121,7 @@ class FormClassesTestCase(TestCase):
Return the base form class for the given model's FilterSet.
"""
if model._meta.app_label == 'dummy_plugin':
return
return None
if issubclass(model, PrimaryModel):
return PrimaryModelFilterSetForm
if issubclass(model, OrganizationalModel):
@@ -127,6 +130,7 @@ class FormClassesTestCase(TestCase):
return NestedGroupModelFilterSetForm
if issubclass(model, NetBoxModel):
return NetBoxModelFilterSetForm
return None
def test_model_form_base_classes(self):
"""
@@ -182,7 +186,7 @@ class FilterSetClassesTestCase(TestCase):
Return the base FilterSet class for the given model.
"""
if model._meta.app_label == 'dummy_plugin':
return
return None
if issubclass(model, PrimaryModel):
return PrimaryModelFilterSet
if issubclass(model, OrganizationalModel):
@@ -191,6 +195,7 @@ class FilterSetClassesTestCase(TestCase):
return NestedGroupModelFilterSet
if issubclass(model, NetBoxModel):
return NetBoxModelFilterSet
return None
def test_model_filterset_base_classes(self):
"""
@@ -222,7 +227,7 @@ class TableClassesTestCase(TestCase):
Return the base table class for the given model.
"""
if model._meta.app_label == 'dummy_plugin':
return
return None
if issubclass(model, PrimaryModel):
return PrimaryModelTable
if issubclass(model, OrganizationalModel):
@@ -231,6 +236,7 @@ class TableClassesTestCase(TestCase):
return NestedGroupModelTable
if issubclass(model, NetBoxModel):
return NetBoxTable
return None
def test_model_table_base_classes(self):
"""
@@ -266,7 +272,7 @@ class SerializerClassesTestCase(TestCase):
Return the base serializer class for the given model.
"""
if model._meta.app_label == 'dummy_plugin':
return
return None
if issubclass(model, PrimaryModel):
return PrimaryModelSerializer
if issubclass(model, OrganizationalModel):
@@ -275,6 +281,7 @@ class SerializerClassesTestCase(TestCase):
return NestedGroupModelSerializer
if issubclass(model, NetBoxModel):
return NetBoxModelSerializer
return None
def test_model_serializer_base_classes(self):
"""
@@ -306,7 +313,7 @@ class GraphQLTypeClassesTestCase(TestCase):
Return the base GraphQL type for the given model.
"""
if model._meta.app_label == 'dummy_plugin':
return
return None
if issubclass(model, PrimaryModel):
return PrimaryObjectType
if issubclass(model, OrganizationalModel):
@@ -315,6 +322,7 @@ class GraphQLTypeClassesTestCase(TestCase):
return NestedGroupObjectType
if issubclass(model, NetBoxModel):
return NetBoxObjectType
return None
def test_model_type_base_classes(self):
"""

View File

@@ -169,19 +169,18 @@ class ObjectListView(BaseMultiObjectView, ActionsMixin, TableMixin):
return self.export_table(table, columns, delimiter=delimiter)
# Render an ExportTemplate
elif request.GET['export']:
if request.GET['export']:
template = get_object_or_404(ExportTemplate, object_types=object_type, name=request.GET['export'])
return self.export_template(template, request)
# Check for YAML export support on the model
elif hasattr(model, 'to_yaml'):
if hasattr(model, 'to_yaml'):
response = HttpResponse(self.export_yaml(), content_type='text/yaml')
filename = 'netbox_{}.yaml'.format(self.queryset.model._meta.verbose_name_plural)
response['Content-Disposition'] = 'attachment; filename="{}"'.format(filename)
return response
# Fall back to default table/YAML export
else:
table = self.get_table(self.queryset, request, has_table_actions)
delimiter = request.user.config.get('csv_delimiter')
return self.export_table(table, delimiter=delimiter)
@@ -353,7 +352,7 @@ class BulkImportView(GetReturnURLMixin, BaseMultiObjectView):
for field in form.visible_fields():
if field.is_hidden:
continue
elif field.field.required:
if field.field.required:
required_fields[field.name] = field.field
else:
optional_fields[field.name] = field.field
@@ -581,7 +580,7 @@ class BulkImportView(GetReturnURLMixin, BaseMultiObjectView):
# Handle background job
if is_background_request(request):
request.job.logger.info(msg)
return
return None
messages.success(request, msg)
return redirect(f"{redirect_url}?modified_by_request={request.id}")
@@ -787,7 +786,7 @@ class BulkEditView(GetReturnURLMixin, BaseMultiObjectView):
# Handle background job
if is_background_request(request):
request.job.logger.info(msg)
return
return None
messages.success(self.request, msg)
return redirect(self.get_return_url(request))
@@ -1010,7 +1009,7 @@ class BulkDeleteView(GetReturnURLMixin, BaseMultiObjectView):
# Handle background job
if is_background_request(request):
request.job.logger.info(msg)
return
return None
messages.success(request, msg)
@@ -1032,7 +1031,6 @@ class BulkDeleteView(GetReturnURLMixin, BaseMultiObjectView):
return redirect(self.get_return_url(request))
else:
logger.debug("Form validation failed")
else:

View File

@@ -413,7 +413,6 @@ class ObjectDeleteView(GetReturnURLMixin, BaseObjectView):
return HttpResponse(headers={
'HX-Redirect': obj.get_absolute_url(),
})
else:
return redirect(obj.get_absolute_url())
#
@@ -499,7 +498,6 @@ class ObjectDeleteView(GetReturnURLMixin, BaseObjectView):
return redirect(return_url)
return redirect(self.get_return_url(request, obj))
else:
logger.debug("Form validation failed")
return render(request, self.template_name, {
@@ -607,7 +605,6 @@ class ComponentCreateView(GetReturnURLMixin, BaseObjectView):
# Redirect user on success
if '_addanother' in request.POST and safe_for_redirect(request.get_full_path()):
return redirect(request.get_full_path())
else:
return redirect(self.get_return_url(request))
except (AbortRequest, PermissionsViolation) as e:

View File

@@ -11,3 +11,4 @@ def get_prerequisite_model(queryset):
model = apps.get_model(prereq)
if not model.objects.exists():
return model
return None

View File

@@ -250,7 +250,6 @@ class ObjectPermissionFilterSet(BaseFilterSet):
action = name.split('_')[1]
if value:
return queryset.filter(actions__contains=[action])
else:
return queryset.exclude(actions__contains=[action])

View File

@@ -209,6 +209,7 @@ class Token(models.Model):
return 'Token '
if self.v2:
return f'Bearer {TOKEN_PREFIX}{self.key}.'
return None
def clean(self):
super().clean()
@@ -288,6 +289,7 @@ class Token(models.Model):
return False
digest = hmac.new(pepper.encode('utf-8'), token.encode('utf-8'), hashlib.sha256).hexdigest()
return digest == self.hmac_digest
return False
def validate_client_ip(self, client_ip):
"""

View File

@@ -133,7 +133,6 @@ class RestrictedGenericForeignKey(GenericForeignKey):
ct_id = getattr(obj, ct_attname)
if ct_id is None:
return None
else:
if model := self.get_content_type(
id=ct_id, using=obj._state.db
).model_class():

View File

@@ -50,6 +50,6 @@ class ExpandableIPAddressField(forms.CharField):
# Hackish address family detection but it's all we have to work with
if '.' in value and re.search(IP4_EXPANSION_PATTERN, value):
return list(expand_ipaddress_pattern(value, 4))
elif ':' in value and re.search(IP6_EXPANSION_PATTERN, value):
if ':' in value and re.search(IP6_EXPANSION_PATTERN, value):
return list(expand_ipaddress_pattern(value, 6))
return [value]

View File

@@ -139,7 +139,7 @@ def get_field_value(form, field_name):
if form.is_bound and field_name in form.data:
if (value := form.data[field_name]) is None:
return
return None
if hasattr(field, 'valid_value') and field.valid_value(value):
return value

View File

@@ -37,7 +37,6 @@ def foreground_color(bg_color, dark='000000', light='ffffff'):
r, g, b = [int(bg_color[c:c + 2], 16) for c in (0, 2, 4)]
if r * 0.299 + g * 0.587 + b * 0.114 > THRESHOLD:
return dark
else:
return light

View File

@@ -25,7 +25,7 @@ def process_request_as_job(view, request, name=None):
# Check that the request that is not already being processed as a background job (would be a loop)
if is_background_request(request):
return
return None
# Create a serializable copy of the original request
request_copy = copy_safe_request(request)

View File

@@ -54,3 +54,4 @@ def resolve_proxies(url=None, protocol=None, context=None):
router = import_string(item) if type(item) is str else item
if proxies := router().route(url=url, protocol=protocol, context=context):
return proxies
return None

View File

@@ -67,7 +67,7 @@ def reapply_model_ordering(queryset: QuerySet) -> QuerySet:
# MPTT-based models are exempt from this; use caution when annotating querysets of these models
if any(isinstance(manager, TreeManager) for manager in queryset.model._meta.local_managers):
return queryset
elif queryset.ordered:
if queryset.ordered:
return queryset
ordering = queryset.model._meta.ordering

View File

@@ -34,3 +34,4 @@ def get_rq_retry():
retry_interval = get_config().RQ_RETRY_INTERVAL
if retry_max:
return Retry(max=retry_max, interval=retry_interval)
return None

View File

@@ -33,13 +33,13 @@ def get_table_for_model(model, name=None):
try:
return import_string(f'{model._meta.app_label}.tables.{name}')
except ImportError:
return
return None
def get_table_ordering(request, table):
"""
Given a request, return the prescribed table ordering, if any. This may be necessary to determine prior to rendering
the table itself.
Given a request, return the prescribed table ordering, if any.
This may be necessary to determine before rendering the table itself.
"""
# Check for an explicit ordering
if 'sort' in request.GET:
@@ -49,6 +49,7 @@ def get_table_ordering(request, table):
if request.user.is_authenticated:
if preference := request.user.config.get(f'tables.{table.__name__}.ordering'):
return preference
return None
def linkify_phone(value):

View File

@@ -227,11 +227,10 @@ def isodate(value):
if type(value) is datetime.date:
text = value.isoformat()
return mark_safe(f'<span title="{naturalday(value)}">{text}</span>')
elif type(value) is datetime.datetime:
if type(value) is datetime.datetime:
local_value = localtime(value) if value.tzinfo else value
text = local_value.date().isoformat()
return mark_safe(f'<span title="{naturaltime(value)}">{text}</span>')
else:
return ''

View File

@@ -37,9 +37,8 @@ def widget_type(field):
"""
if hasattr(field, 'widget'):
return field.widget.__class__.__name__.lower()
elif hasattr(field, 'field'):
if hasattr(field, 'field'):
return field.field.widget.__class__.__name__.lower()
else:
return None

View File

@@ -199,13 +199,12 @@ def humanize_speed(speed):
return ''
if speed >= 1000000000 and speed % 1000000000 == 0:
return '{} Tbps'.format(int(speed / 1000000000))
elif speed >= 1000000 and speed % 1000000 == 0:
if speed >= 1000000 and speed % 1000000 == 0:
return '{} Gbps'.format(int(speed / 1000000))
elif speed >= 1000 and speed % 1000 == 0:
if speed >= 1000 and speed % 1000 == 0:
return '{} Mbps'.format(int(speed / 1000))
elif speed >= 1000:
if speed >= 1000:
return '{} Mbps'.format(float(speed) / 1000)
else:
return '{} Kbps'.format(speed)
@@ -373,7 +372,6 @@ def querystring(request, **kwargs):
querystring = querydict.urlencode(safe='/')
if querystring:
return '?' + querystring
else:
return ''

View File

@@ -66,7 +66,7 @@ class BaseFilterSetTests:
return [(f'{filter_name}_id', django_filters.ModelMultipleChoiceFilter)]
# Many-to-many relationships (forward & backward)
elif type(field) in (ManyToManyField, ManyToManyRel):
if type(field) in (ManyToManyField, ManyToManyRel):
filter_name = self.get_m2m_filter_name(field)
filter_name = self.filter_name_map.get(filter_name, filter_name)

View File

@@ -635,9 +635,8 @@ class ViewTestCases:
available = ', '.join(self.csv_data.keys())
raise ValueError(f"Scenario '{scenario_name}' not found in csv_data. Available: {available}")
return '\n'.join(self.csv_data[scenario_name])
elif isinstance(self.csv_data, (tuple, list)):
if isinstance(self.csv_data, (tuple, list)):
return '\n'.join(self.csv_data)
else:
raise TypeError(f'csv_data must be a tuple, list, or dictionary, got {type(self.csv_data)}')
def _get_update_csv_data(self):

View File

@@ -252,7 +252,6 @@ class VMInterfaceImportForm(OwnerCSVMixin, NetBoxModelImportForm):
# Make sure enabled is True when it's not included in the uploaded data
if 'enabled' not in self.data:
return True
else:
return self.cleaned_data['enabled']

View File

@@ -260,11 +260,10 @@ class VirtualMachine(ContactsMixin, ImageAttachmentsMixin, RenderConfigMixin, Co
def primary_ip(self):
if get_config().PREFER_IPV4 and self.primary_ip4:
return self.primary_ip4
elif self.primary_ip6:
if self.primary_ip6:
return self.primary_ip6
elif self.primary_ip4:
if self.primary_ip4:
return self.primary_ip4
else:
return None

View File

@@ -80,7 +80,6 @@ class L2VPN(ContactsMixin, PrimaryModel):
def can_add_termination(self):
if self.type in L2VPNTypeChoices.P2P and self.terminations.count() >= 2:
return False
else:
return True
@@ -151,9 +150,9 @@ class L2VPNTermination(NetBoxModel):
obj_type = ObjectType.objects.get_for_model(self.assigned_object)
if obj_type.model == 'vminterface':
return self.assigned_object.virtual_machine
elif obj_type.model == 'interface':
if obj_type.model == 'interface':
return self.assigned_object.device
elif obj_type.model == 'vminterface':
if obj_type.model == 'vminterface':
return self.assigned_object.virtual_machine
return None

View File

@@ -37,10 +37,12 @@ extend-select = [
"E501", # pycodestyle: line too long (enforced with `line-length` above)
"W", # pycodestyle warnings (various style warnings, often whitespace/newlines)
"I", # import sorting (isort-equivalent)
"RET", # return semantics (flake8-return family)
]
ignore = [
"F403", # pyflakes: `from ... import *` used; unable to detect undefined names
"F405", # pyflakes: name may be undefined or defined from star imports
"RET504", # return: unnecessary assignment before `return` (e.g., `x = expr; return x` -> `return expr`)
"UP032", # pyupgrade: prefer f-strings over `str.format(...)`
]
preview = true