mirror of
https://github.com/netbox-community/netbox.git
synced 2026-04-25 18:29:20 +02:00
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:
committed by
Jeremy Stretch
parent
b22e490847
commit
ef52ac4203
@@ -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)}")
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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])
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 []
|
||||
|
||||
|
||||
@@ -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")
|
||||
)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -117,6 +117,7 @@ class CustomFieldChoiceSetImportForm(OwnerCSVMixin, CSVModelForm):
|
||||
value, label = line, line
|
||||
data.append((value, label))
|
||||
return data
|
||||
return None
|
||||
|
||||
|
||||
class CustomLinkImportForm(OwnerCSVMixin, CSVModelForm):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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']:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
|
||||
@@ -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 == '':
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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}'
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -84,6 +84,7 @@ class SearchIndex:
|
||||
"""
|
||||
if value := getattr(instance, field_name):
|
||||
return str(value)
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def get_category(cls):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -11,3 +11,4 @@ def get_prerequisite_model(queryset):
|
||||
model = apps.get_model(prereq)
|
||||
if not model.objects.exists():
|
||||
return model
|
||||
return None
|
||||
|
||||
@@ -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])
|
||||
|
||||
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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 ''
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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 ''
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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']
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user