Fix E501 errors

This commit is contained in:
Jeremy Stretch 2024-11-21 14:57:20 -05:00
parent d39dc6d45c
commit 3b7d8dd5db
27 changed files with 551 additions and 147 deletions

View File

@ -351,9 +351,9 @@ class InventoryItemSerializer(NetBoxModelSerializer):
class Meta: class Meta:
model = InventoryItem model = InventoryItem
fields = [ fields = [
'id', 'url', 'display_url', 'display', 'device', 'parent', 'name', 'label', 'status', 'role', 'manufacturer', 'id', 'url', 'display_url', 'display', 'device', 'parent', 'name', 'label', 'status', 'role',
'part_id', 'serial', 'asset_tag', 'discovered', 'description', 'component_type', 'component_id', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered', 'description', 'component_type',
'component', 'tags', 'custom_fields', 'created', 'last_updated', '_depth', 'component_id', 'component', 'tags', 'custom_fields', 'created', 'last_updated', '_depth',
] ]
brief_fields = ('id', 'url', 'display', 'device', 'name', 'description', '_depth') brief_fields = ('id', 'url', 'display', 'device', 'name', 'description', '_depth')

View File

@ -312,8 +312,8 @@ class RackTypeFilterSet(NetBoxModelFilterSet):
class Meta: class Meta:
model = RackType model = RackType
fields = ( fields = (
'id', 'model', 'slug', 'u_height', 'starting_unit', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'id', 'model', 'slug', 'u_height', 'starting_unit', 'desc_units', 'outer_width', 'outer_depth',
'mounting_depth', 'weight', 'max_weight', 'weight_unit', 'description', 'outer_unit', 'mounting_depth', 'weight', 'max_weight', 'weight_unit', 'description',
) )
def search(self, queryset, name, value): def search(self, queryset, name, value):

View File

@ -428,7 +428,10 @@ class ModuleTypeImportForm(NetBoxModelImportForm):
class Meta: class Meta:
model = ModuleType model = ModuleType
fields = ['manufacturer', 'model', 'part_number', 'description', 'airflow', 'weight', 'weight_unit', 'comments', 'tags'] fields = [
'manufacturer', 'model', 'part_number', 'description', 'airflow', 'weight', 'weight_unit', 'comments',
'tags',
]
class DeviceRoleImportForm(NetBoxModelImportForm): class DeviceRoleImportForm(NetBoxModelImportForm):
@ -800,7 +803,10 @@ class PowerOutletImportForm(NetBoxModelImportForm):
class Meta: class Meta:
model = PowerOutlet model = PowerOutlet
fields = ('device', 'name', 'label', 'type', 'color', 'mark_connected', 'power_port', 'feed_leg', 'description', 'tags') fields = (
'device', 'name', 'label', 'type', 'color', 'mark_connected', 'power_port', 'feed_leg', 'description',
'tags',
)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@ -1114,8 +1120,8 @@ class InventoryItemImportForm(NetBoxModelImportForm):
class Meta: class Meta:
model = InventoryItem model = InventoryItem
fields = ( fields = (
'device', 'name', 'label', 'status', 'role', 'manufacturer', 'parent', 'part_id', 'serial', 'asset_tag', 'discovered', 'device', 'name', 'label', 'status', 'role', 'manufacturer', 'parent', 'part_id', 'serial', 'asset_tag',
'description', 'tags', 'component_type', 'component_name', 'discovered', 'description', 'tags', 'component_type', 'component_name',
) )
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):

View File

@ -136,7 +136,10 @@ class ModuleCommonForm(forms.Form):
if len(module_bays) != template.name.count(MODULE_TOKEN): if len(module_bays) != template.name.count(MODULE_TOKEN):
raise forms.ValidationError( raise forms.ValidationError(
_("Cannot install module with placeholder values in a module bay tree {level} in tree but {tokens} placeholders given.").format( _(
"Cannot install module with placeholder values in a module bay tree {level} in tree "
"but {tokens} placeholders given."
).format(
level=len(module_bays), tokens=template.name.count(MODULE_TOKEN) level=len(module_bays), tokens=template.name.count(MODULE_TOKEN)
) )
) )

View File

@ -111,9 +111,15 @@ def get_cable_form(a_type, b_type):
if self.instance and self.instance.pk: if self.instance and self.instance.pk:
# Initialize A/B terminations when modifying an existing Cable instance # Initialize A/B terminations when modifying an existing Cable instance
if a_type and self.instance.a_terminations and a_ct == ContentType.objects.get_for_model(self.instance.a_terminations[0]): if (
a_type and self.instance.a_terminations and
a_ct == ContentType.objects.get_for_model(self.instance.a_terminations[0])
):
self.initial['a_terminations'] = self.instance.a_terminations self.initial['a_terminations'] = self.instance.a_terminations
if b_type and self.instance.b_terminations and b_ct == ContentType.objects.get_for_model(self.instance.b_terminations[0]): if (
b_type and self.instance.b_terminations and
b_ct == ContentType.objects.get_for_model(self.instance.b_terminations[0])
):
self.initial['b_terminations'] = self.instance.b_terminations self.initial['b_terminations'] = self.instance.b_terminations
else: else:
# Need to clear terminations if swapped type - but need to do it only # Need to clear terminations if swapped type - but need to do it only

View File

@ -266,7 +266,10 @@ class RackForm(TenancyForm, NetBoxModelForm):
comments = CommentField() comments = CommentField()
fieldsets = ( fieldsets = (
FieldSet('site', 'location', 'name', 'status', 'role', 'rack_type', 'description', 'airflow', 'tags', name=_('Rack')), FieldSet(
'site', 'location', 'name', 'status', 'role', 'rack_type', 'description', 'airflow', 'tags',
name=_('Rack')
),
FieldSet('facility_id', 'serial', 'asset_tag', name=_('Inventory Control')), FieldSet('facility_id', 'serial', 'asset_tag', name=_('Inventory Control')),
FieldSet('tenant_group', 'tenant', name=_('Tenancy')), FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
) )
@ -1007,7 +1010,8 @@ class InterfaceTemplateForm(ModularComponentTemplateForm):
class Meta: class Meta:
model = InterfaceTemplate model = InterfaceTemplate
fields = [ fields = [
'device_type', 'module_type', 'name', 'label', 'type', 'mgmt_only', 'enabled', 'description', 'poe_mode', 'poe_type', 'bridge', 'rf_role', 'device_type', 'module_type', 'name', 'label', 'type', 'mgmt_only', 'enabled', 'description', 'poe_mode',
'poe_type', 'bridge', 'rf_role',
] ]
@ -1189,7 +1193,10 @@ class InventoryItemTemplateForm(ComponentTemplateForm):
break break
elif component_type and component_id: elif component_type and component_id:
# When adding the InventoryItem from a component page # When adding the InventoryItem from a component page
if content_type := ContentType.objects.filter(MODULAR_COMPONENT_TEMPLATE_MODELS).filter(pk=component_type).first(): content_type = ContentType.objects.filter(
MODULAR_COMPONENT_TEMPLATE_MODELS
).filter(pk=component_type).first()
if content_type:
if component := content_type.model_class().objects.filter(pk=component_id).first(): if component := content_type.model_class().objects.filter(pk=component_id).first():
initial[content_type.model] = component initial[content_type.model] = component
@ -1301,16 +1308,16 @@ class PowerOutletForm(ModularDeviceComponentForm):
fieldsets = ( fieldsets = (
FieldSet( FieldSet(
'device', 'module', 'name', 'label', 'type', 'color', 'power_port', 'feed_leg', 'mark_connected', 'description', 'device', 'module', 'name', 'label', 'type', 'color', 'power_port', 'feed_leg', 'mark_connected',
'tags', 'description', 'tags',
), ),
) )
class Meta: class Meta:
model = PowerOutlet model = PowerOutlet
fields = [ fields = [
'device', 'module', 'name', 'label', 'type', 'color', 'power_port', 'feed_leg', 'mark_connected', 'description', 'device', 'module', 'name', 'label', 'type', 'color', 'power_port', 'feed_leg', 'mark_connected',
'tags', 'description', 'tags',
] ]
@ -1611,7 +1618,10 @@ class InventoryItemForm(DeviceComponentForm):
) )
fieldsets = ( fieldsets = (
FieldSet('device', 'parent', 'name', 'label', 'status', 'role', 'description', 'tags', name=_('Inventory Item')), FieldSet(
'device', 'parent', 'name', 'label', 'status', 'role', 'description', 'tags',
name=_('Inventory Item')
),
FieldSet('manufacturer', 'part_id', 'serial', 'asset_tag', name=_('Hardware')), FieldSet('manufacturer', 'part_id', 'serial', 'asset_tag', name=_('Hardware')),
FieldSet( FieldSet(
TabbedGroups( TabbedGroups(

View File

@ -416,7 +416,8 @@ class VirtualChassisCreateForm(NetBoxModelForm):
class Meta: class Meta:
model = VirtualChassis model = VirtualChassis
fields = [ fields = [
'name', 'domain', 'description', 'region', 'site_group', 'site', 'rack', 'members', 'initial_position', 'tags', 'name', 'domain', 'description', 'region', 'site_group', 'site', 'rack', 'members', 'initial_position',
'tags',
] ]
def clean(self): def clean(self):

View File

@ -136,7 +136,8 @@ class FrontPortTemplateImportForm(forms.ModelForm):
class Meta: class Meta:
model = FrontPortTemplate model = FrontPortTemplate
fields = [ fields = [
'device_type', 'module_type', 'name', 'type', 'color', 'rear_port', 'rear_port_position', 'label', 'description', 'device_type', 'module_type', 'name', 'type', 'color', 'rear_port', 'rear_port_position', 'label',
'description',
] ]

View File

@ -482,7 +482,9 @@ class LocationType(VLANGroupsMixin, ImageAttachmentsMixin, ContactsMixin, Organi
return self.cluster_set.all() return self.cluster_set.all()
@strawberry_django.field @strawberry_django.field
def circuit_terminations(self) -> List[Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')]]: def circuit_terminations(self) -> List[
Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')]
]:
return self.circuit_terminations.all() return self.circuit_terminations.all()
@ -728,7 +730,9 @@ class RegionType(VLANGroupsMixin, ContactsMixin, OrganizationalObjectType):
return self.cluster_set.all() return self.cluster_set.all()
@strawberry_django.field @strawberry_django.field
def circuit_terminations(self) -> List[Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')]]: def circuit_terminations(self) -> List[
Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')]
]:
return self.circuit_terminations.all() return self.circuit_terminations.all()
@ -760,7 +764,9 @@ class SiteType(VLANGroupsMixin, ImageAttachmentsMixin, ContactsMixin, NetBoxObje
return self.cluster_set.all() return self.cluster_set.all()
@strawberry_django.field @strawberry_django.field
def circuit_terminations(self) -> List[Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')]]: def circuit_terminations(self) -> List[
Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')]
]:
return self.circuit_terminations.all() return self.circuit_terminations.all()
@ -784,7 +790,9 @@ class SiteGroupType(VLANGroupsMixin, ContactsMixin, OrganizationalObjectType):
return self.cluster_set.all() return self.cluster_set.all()
@strawberry_django.field @strawberry_django.field
def circuit_terminations(self) -> List[Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')]]: def circuit_terminations(self) -> List[
Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')]
]:
return self.circuit_terminations.all() return self.circuit_terminations.all()

View File

@ -912,7 +912,10 @@ class Device(
}) })
if self.primary_ip4.assigned_object in vc_interfaces: if self.primary_ip4.assigned_object in vc_interfaces:
pass pass
elif self.primary_ip4.nat_inside is not None and self.primary_ip4.nat_inside.assigned_object in vc_interfaces: elif (
self.primary_ip4.nat_inside is not None and
self.primary_ip4.nat_inside.assigned_object in vc_interfaces
):
pass pass
else: else:
raise ValidationError({ raise ValidationError({
@ -927,7 +930,10 @@ class Device(
}) })
if self.primary_ip6.assigned_object in vc_interfaces: if self.primary_ip6.assigned_object in vc_interfaces:
pass pass
elif self.primary_ip6.nat_inside is not None and self.primary_ip6.nat_inside.assigned_object in vc_interfaces: elif (
self.primary_ip6.nat_inside is not None and
self.primary_ip6.nat_inside.assigned_object in vc_interfaces
):
pass pass
else: else:
raise ValidationError({ raise ValidationError({

View File

@ -153,7 +153,10 @@ class RackElevationSVG:
if self.rack.desc_units: if self.rack.desc_units:
y += int((position - self.rack.starting_unit) * self.unit_height) y += int((position - self.rack.starting_unit) * self.unit_height)
else: else:
y += int((self.rack.u_height - position + self.rack.starting_unit) * self.unit_height) - int(height * self.unit_height) y += (
int((self.rack.u_height - position + self.rack.starting_unit) * self.unit_height) -
int(height * self.unit_height)
)
return x, y return x, y

View File

@ -53,7 +53,8 @@ class Command(BaseCommand):
else: else:
raise CommandError( raise CommandError(
f"Invalid model: {label}. Model names must be in the format <app_label> or <app_label>.<model_name>." f"Invalid model: {label}. Model names must be in the format <app_label> or "
f"<app_label>.<model_name>."
) )
return indexers return indexers

View File

@ -27,13 +27,15 @@ class Migration(migrations.Migration):
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='core.objecttype' blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='core.objecttype'
), ),
), ),
migrations.RunSQL( migrations.RunSQL((
'ALTER TABLE IF EXISTS extras_customfield_content_types_id_seq RENAME TO extras_customfield_object_types_id_seq' 'ALTER TABLE IF EXISTS extras_customfield_content_types_id_seq '
), 'RENAME TO extras_customfield_object_types_id_seq'
)),
# Pre-v2.10 sequence name (see #15605) # Pre-v2.10 sequence name (see #15605)
migrations.RunSQL( migrations.RunSQL((
'ALTER TABLE IF EXISTS extras_customfield_obj_type_id_seq RENAME TO extras_customfield_object_types_id_seq' 'ALTER TABLE IF EXISTS extras_customfield_obj_type_id_seq '
), 'RENAME TO extras_customfield_object_types_id_seq'
)),
# Custom links # Custom links
migrations.RenameField( migrations.RenameField(
model_name='customlink', model_name='customlink',

View File

@ -704,7 +704,10 @@ class JournalEntry(CustomFieldsMixin, CustomLinksMixin, TagsMixin, ExportTemplat
def __str__(self): def __str__(self):
created = timezone.localtime(self.created) created = timezone.localtime(self.created)
return f"{created.date().isoformat()} {created.time().isoformat(timespec='minutes')} ({self.get_kind_display()})" return (
f"{created.date().isoformat()} {created.time().isoformat(timespec='minutes')} "
f"({self.get_kind_display()})"
)
def get_absolute_url(self): def get_absolute_url(self):
return reverse('extras:journalentry', args=[self.pk]) return reverse('extras:journalentry', args=[self.pk])

View File

@ -637,15 +637,51 @@ class CustomFieldAPITest(APITestCase):
) )
custom_fields = ( custom_fields = (
CustomField(type=CustomFieldTypeChoices.TYPE_TEXT, name='text_field', default='foo'), CustomField(
CustomField(type=CustomFieldTypeChoices.TYPE_LONGTEXT, name='longtext_field', default='ABC'), type=CustomFieldTypeChoices.TYPE_TEXT,
CustomField(type=CustomFieldTypeChoices.TYPE_INTEGER, name='integer_field', default=123), name='text_field',
CustomField(type=CustomFieldTypeChoices.TYPE_DECIMAL, name='decimal_field', default=123.45), default='foo'
CustomField(type=CustomFieldTypeChoices.TYPE_BOOLEAN, name='boolean_field', default=False), ),
CustomField(type=CustomFieldTypeChoices.TYPE_DATE, name='date_field', default='2020-01-01'), CustomField(
CustomField(type=CustomFieldTypeChoices.TYPE_DATETIME, name='datetime_field', default='2020-01-01T01:23:45'), type=CustomFieldTypeChoices.TYPE_LONGTEXT,
CustomField(type=CustomFieldTypeChoices.TYPE_URL, name='url_field', default='http://example.com/1'), name='longtext_field',
CustomField(type=CustomFieldTypeChoices.TYPE_JSON, name='json_field', default='{"x": "y"}'), default='ABC'
),
CustomField(
type=CustomFieldTypeChoices.TYPE_INTEGER,
name='integer_field',
default=123
),
CustomField(
type=CustomFieldTypeChoices.TYPE_DECIMAL,
name='decimal_field',
default=123.45
),
CustomField(
type=CustomFieldTypeChoices.TYPE_BOOLEAN,
name='boolean_field',
default=False)
,
CustomField(
type=CustomFieldTypeChoices.TYPE_DATE,
name='date_field',
default='2020-01-01'
),
CustomField(
type=CustomFieldTypeChoices.TYPE_DATETIME,
name='datetime_field',
default='2020-01-01T01:23:45'
),
CustomField(
type=CustomFieldTypeChoices.TYPE_URL,
name='url_field',
default='http://example.com/1'
),
CustomField(
type=CustomFieldTypeChoices.TYPE_JSON,
name='json_field',
default='{"x": "y"}'
),
CustomField( CustomField(
type=CustomFieldTypeChoices.TYPE_SELECT, type=CustomFieldTypeChoices.TYPE_SELECT,
name='select_field', name='select_field',
@ -656,7 +692,7 @@ class CustomFieldAPITest(APITestCase):
type=CustomFieldTypeChoices.TYPE_MULTISELECT, type=CustomFieldTypeChoices.TYPE_MULTISELECT,
name='multiselect_field', name='multiselect_field',
default=['foo'], default=['foo'],
choice_set=choice_set choice_set=choice_set,
), ),
CustomField( CustomField(
type=CustomFieldTypeChoices.TYPE_OBJECT, type=CustomFieldTypeChoices.TYPE_OBJECT,
@ -1273,9 +1309,18 @@ class CustomFieldImportTest(TestCase):
Import a Site in CSV format, including a value for each CustomField. Import a Site in CSV format, including a value for each CustomField.
""" """
data = ( data = (
('name', 'slug', 'status', 'cf_text', 'cf_longtext', 'cf_integer', 'cf_decimal', 'cf_boolean', 'cf_date', 'cf_datetime', 'cf_url', 'cf_json', 'cf_select', 'cf_multiselect'), (
('Site 1', 'site-1', 'active', 'ABC', 'Foo', '123', '123.45', 'True', '2020-01-01', '2020-01-01 12:00:00', 'http://example.com/1', '{"foo": 123}', 'a', '"a,b"'), 'name', 'slug', 'status', 'cf_text', 'cf_longtext', 'cf_integer', 'cf_decimal', 'cf_boolean', 'cf_date',
('Site 2', 'site-2', 'active', 'DEF', 'Bar', '456', '456.78', 'False', '2020-01-02', '2020-01-02 12:00:00', 'http://example.com/2', '{"bar": 456}', 'b', '"b,c"'), 'cf_datetime', 'cf_url', 'cf_json', 'cf_select', 'cf_multiselect',
),
(
'Site 1', 'site-1', 'active', 'ABC', 'Foo', '123', '123.45', 'True', '2020-01-01',
'2020-01-01 12:00:00', 'http://example.com/1', '{"foo": 123}', 'a', '"a,b"',
),
(
'Site 2', 'site-2', 'active', 'DEF', 'Bar', '456', '456.78', 'False', '2020-01-02',
'2020-01-02 12:00:00', 'http://example.com/2', '{"bar": 456}', 'b', '"b,c"',
),
('Site 3', 'site-3', 'active', '', '', '', '', '', '', '', '', '', '', ''), ('Site 3', 'site-3', 'active', '', '', '', '', '', '', '', '', '', '', ''),
) )
csv_data = '\n'.join(','.join(row) for row in data) csv_data = '\n'.join(','.join(row) for row in data)
@ -1616,7 +1661,10 @@ class CustomFieldModelFilterTest(TestCase):
self.assertEqual(self.filterset({'cf_cf6__lte': ['2016-06-27']}, self.queryset).qs.count(), 2) self.assertEqual(self.filterset({'cf_cf6__lte': ['2016-06-27']}, self.queryset).qs.count(), 2)
def test_filter_url_strict(self): def test_filter_url_strict(self):
self.assertEqual(self.filterset({'cf_cf7': ['http://a.example.com', 'http://b.example.com']}, self.queryset).qs.count(), 2) self.assertEqual(
self.filterset({'cf_cf7': ['http://a.example.com', 'http://b.example.com']}, self.queryset).qs.count(),
2
)
self.assertEqual(self.filterset({'cf_cf7__n': ['http://b.example.com']}, self.queryset).qs.count(), 2) self.assertEqual(self.filterset({'cf_cf7__n': ['http://b.example.com']}, self.queryset).qs.count(), 2)
self.assertEqual(self.filterset({'cf_cf7__ic': ['b']}, self.queryset).qs.count(), 1) self.assertEqual(self.filterset({'cf_cf7__ic': ['b']}, self.queryset).qs.count(), 1)
self.assertEqual(self.filterset({'cf_cf7__nic': ['b']}, self.queryset).qs.count(), 2) self.assertEqual(self.filterset({'cf_cf7__nic': ['b']}, self.queryset).qs.count(), 2)
@ -1640,9 +1688,18 @@ class CustomFieldModelFilterTest(TestCase):
def test_filter_object(self): def test_filter_object(self):
manufacturer_ids = Manufacturer.objects.values_list('id', flat=True) manufacturer_ids = Manufacturer.objects.values_list('id', flat=True)
self.assertEqual(self.filterset({'cf_cf11': [manufacturer_ids[0], manufacturer_ids[1]]}, self.queryset).qs.count(), 2) self.assertEqual(
self.filterset({'cf_cf11': [manufacturer_ids[0], manufacturer_ids[1]]}, self.queryset).qs.count(),
2
)
def test_filter_multiobject(self): def test_filter_multiobject(self):
manufacturer_ids = Manufacturer.objects.values_list('id', flat=True) manufacturer_ids = Manufacturer.objects.values_list('id', flat=True)
self.assertEqual(self.filterset({'cf_cf12': [manufacturer_ids[0], manufacturer_ids[1]]}, self.queryset).qs.count(), 2) self.assertEqual(
self.assertEqual(self.filterset({'cf_cf12': [manufacturer_ids[3]]}, self.queryset).qs.count(), 3) self.filterset({'cf_cf12': [manufacturer_ids[0], manufacturer_ids[1]]}, self.queryset).qs.count(),
2
)
self.assertEqual(
self.filterset({'cf_cf12': [manufacturer_ids[3]]}, self.queryset).qs.count(),
3
)

View File

@ -387,7 +387,12 @@ class IPAddressForm(TenancyForm, NetBoxModelForm):
}) })
elif selected_objects: elif selected_objects:
assigned_object = self.cleaned_data[selected_objects[0]] assigned_object = self.cleaned_data[selected_objects[0]]
if self.instance.pk and self.instance.assigned_object and self.cleaned_data['primary_for_parent'] and assigned_object != self.instance.assigned_object: if (
self.instance.pk and
self.instance.assigned_object and
self.cleaned_data['primary_for_parent'] and
assigned_object != self.instance.assigned_object
):
raise ValidationError( raise ValidationError(
_("Cannot reassign IP address while it is designated as the primary IP for the parent object") _("Cannot reassign IP address while it is designated as the primary IP for the parent object")
) )

View File

@ -295,7 +295,10 @@ class VLANTranslationPolicyType(NetBoxObjectType):
filters=VLANTranslationRuleFilter filters=VLANTranslationRuleFilter
) )
class VLANTranslationRuleType(NetBoxObjectType): class VLANTranslationRuleType(NetBoxObjectType):
policy: Annotated["VLANTranslationPolicyType", strawberry.lazy('ipam.graphql.types')] = strawberry_django.field(select_related=["policy"]) policy: Annotated[
"VLANTranslationPolicyType",
strawberry.lazy('ipam.graphql.types')
] = strawberry_django.field(select_related=["policy"])
@strawberry_django.type( @strawberry_django.type(

View File

@ -57,7 +57,10 @@ class Migration(migrations.Migration):
validators=[ validators=[
django.core.validators.RegexValidator( django.core.validators.RegexValidator(
code='invalid', code='invalid',
message='Only alphanumeric characters, asterisks, hyphens, periods, and underscores are allowed in DNS names', message=(
'Only alphanumeric characters, asterisks, hyphens, periods, and underscores are '
'allowed in DNS names'
),
regex='^([0-9A-Za-z_-]+|\\*)(\\.[0-9A-Za-z_-]+)*\\.?$', regex='^([0-9A-Za-z_-]+|\\*)(\\.[0-9A-Za-z_-]+)*\\.?$',
) )
], ],

View File

@ -418,7 +418,9 @@ class Prefix(ContactsMixin, GetAvailablePrefixesMixin, CachedScopeMixin, Primary
available_ips = prefix - child_ips - netaddr.IPSet(child_ranges) available_ips = prefix - child_ips - netaddr.IPSet(child_ranges)
# IPv6 /127's, pool, or IPv4 /31-/32 sets are fully usable # IPv6 /127's, pool, or IPv4 /31-/32 sets are fully usable
if (self.family == 6 and self.prefix.prefixlen >= 127) or self.is_pool or (self.family == 4 and self.prefix.prefixlen >= 31): if (self.family == 6 and self.prefix.prefixlen >= 127) or self.is_pool or (
self.family == 4 and self.prefix.prefixlen >= 31
):
return available_ips return available_ips
if self.family == 4: if self.family == 4:
@ -561,10 +563,26 @@ class IPRange(ContactsMixin, PrimaryModel):
}) })
# Check for overlapping ranges # Check for overlapping ranges
overlapping_ranges = IPRange.objects.exclude(pk=self.pk).filter(vrf=self.vrf).filter( overlapping_ranges = (
Q(start_address__host__inet__gte=self.start_address.ip, start_address__host__inet__lte=self.end_address.ip) | # Starts inside IPRange.objects.exclude(pk=self.pk)
Q(end_address__host__inet__gte=self.start_address.ip, end_address__host__inet__lte=self.end_address.ip) | # Ends inside .filter(vrf=self.vrf)
Q(start_address__host__inet__lte=self.start_address.ip, end_address__host__inet__gte=self.end_address.ip) # Starts & ends outside .filter(
# Starts inside
Q(
start_address__host__inet__gte=self.start_address.ip,
start_address__host__inet__lte=self.end_address.ip,
) |
# Ends inside
Q(
end_address__host__inet__gte=self.start_address.ip,
end_address__host__inet__lte=self.end_address.ip,
) |
# Starts & ends outside
Q(
start_address__host__inet__lte=self.start_address.ip,
end_address__host__inet__gte=self.end_address.ip,
)
)
) )
if overlapping_ranges.exists(): if overlapping_ranges.exists():
raise ValidationError( raise ValidationError(
@ -866,10 +884,12 @@ class IPAddress(ContactsMixin, PrimaryModel):
# can't use is_primary_ip as self.assigned_object might be changed # can't use is_primary_ip as self.assigned_object might be changed
is_primary = False is_primary = False
if self.family == 4 and hasattr(original_parent, 'primary_ip4') and original_parent.primary_ip4_id == self.pk: if self.family == 4 and hasattr(original_parent, 'primary_ip4'):
is_primary = True if original_parent.primary_ip4_id == self.pk:
if self.family == 6 and hasattr(original_parent, 'primary_ip6') and original_parent.primary_ip6_id == self.pk: is_primary = True
is_primary = True if self.family == 6 and hasattr(original_parent, 'primary_ip6'):
if original_parent.primary_ip6_id == self.pk:
is_primary = True
if is_primary and (parent != original_parent): if is_primary and (parent != original_parent):
raise ValidationError( raise ValidationError(

View File

@ -732,10 +732,19 @@ class FHRPGroupTest(APIViewTestCases.APIViewTestCase):
@classmethod @classmethod
def setUpTestData(cls): def setUpTestData(cls):
fhrp_groups = ( fhrp_groups = (
FHRPGroup(protocol=FHRPGroupProtocolChoices.PROTOCOL_VRRP2, group_id=10, auth_type=FHRPGroupAuthTypeChoices.AUTHENTICATION_PLAINTEXT, auth_key='foobar123'), FHRPGroup(
FHRPGroup(protocol=FHRPGroupProtocolChoices.PROTOCOL_VRRP3, group_id=20, auth_type=FHRPGroupAuthTypeChoices.AUTHENTICATION_MD5, auth_key='foobar123'), protocol=FHRPGroupProtocolChoices.PROTOCOL_VRRP2,
group_id=10,
auth_type=FHRPGroupAuthTypeChoices.AUTHENTICATION_PLAINTEXT,
auth_key='foobar123',
),
FHRPGroup(
protocol=FHRPGroupProtocolChoices.PROTOCOL_VRRP3,
group_id=20,
auth_type=FHRPGroupAuthTypeChoices.AUTHENTICATION_MD5,
auth_key='foobar123',
),
FHRPGroup(protocol=FHRPGroupProtocolChoices.PROTOCOL_HSRP, group_id=30), FHRPGroup(protocol=FHRPGroupProtocolChoices.PROTOCOL_HSRP, group_id=30),
) )
FHRPGroup.objects.bulk_create(fhrp_groups) FHRPGroup.objects.bulk_create(fhrp_groups)

View File

@ -496,8 +496,12 @@ class AggregateTestCase(TestCase, ChangeLoggedFilterSetTests):
Tenant.objects.bulk_create(tenants) Tenant.objects.bulk_create(tenants)
aggregates = ( aggregates = (
Aggregate(prefix='10.1.0.0/16', rir=rirs[0], tenant=tenants[0], date_added='2020-01-01', description='foobar1'), Aggregate(
Aggregate(prefix='10.2.0.0/16', rir=rirs[0], tenant=tenants[1], date_added='2020-01-02', description='foobar2'), prefix='10.1.0.0/16', rir=rirs[0], tenant=tenants[0], date_added='2020-01-01', description='foobar1'
),
Aggregate(
prefix='10.2.0.0/16', rir=rirs[0], tenant=tenants[1], date_added='2020-01-02', description='foobar2'
),
Aggregate(prefix='10.3.0.0/16', rir=rirs[1], tenant=tenants[2], date_added='2020-01-03'), Aggregate(prefix='10.3.0.0/16', rir=rirs[1], tenant=tenants[2], date_added='2020-01-03'),
Aggregate(prefix='2001:db8:1::/48', rir=rirs[1], tenant=tenants[0], date_added='2020-01-04'), Aggregate(prefix='2001:db8:1::/48', rir=rirs[1], tenant=tenants[0], date_added='2020-01-04'),
Aggregate(prefix='2001:db8:2::/48', rir=rirs[2], tenant=tenants[1], date_added='2020-01-05'), Aggregate(prefix='2001:db8:2::/48', rir=rirs[2], tenant=tenants[1], date_added='2020-01-05'),
@ -656,14 +660,80 @@ class PrefixTestCase(TestCase, ChangeLoggedFilterSetTests):
Tenant.objects.bulk_create(tenants) Tenant.objects.bulk_create(tenants)
prefixes = ( prefixes = (
Prefix(prefix='10.0.0.0/24', tenant=None, scope=None, vrf=None, vlan=None, role=None, is_pool=True, mark_utilized=True, description='foobar1'), Prefix(
Prefix(prefix='10.0.1.0/24', tenant=tenants[0], scope=sites[0], vrf=vrfs[0], vlan=vlans[0], role=roles[0], description='foobar2'), prefix='10.0.0.0/24',
Prefix(prefix='10.0.2.0/24', tenant=tenants[1], scope=sites[1], vrf=vrfs[1], vlan=vlans[1], role=roles[1], status=PrefixStatusChoices.STATUS_DEPRECATED), tenant=None,
Prefix(prefix='10.0.3.0/24', tenant=tenants[2], scope=sites[2], vrf=vrfs[2], vlan=vlans[2], role=roles[2], status=PrefixStatusChoices.STATUS_RESERVED), scope=None,
Prefix(prefix='2001:db8::/64', tenant=None, scope=None, vrf=None, vlan=None, role=None, is_pool=True, mark_utilized=True), vrf=None,
Prefix(prefix='2001:db8:0:1::/64', tenant=tenants[0], scope=sites[0], vrf=vrfs[0], vlan=vlans[0], role=roles[0]), vlan=None,
Prefix(prefix='2001:db8:0:2::/64', tenant=tenants[1], scope=sites[1], vrf=vrfs[1], vlan=vlans[1], role=roles[1], status=PrefixStatusChoices.STATUS_DEPRECATED), role=None,
Prefix(prefix='2001:db8:0:3::/64', tenant=tenants[2], scope=sites[2], vrf=vrfs[2], vlan=vlans[2], role=roles[2], status=PrefixStatusChoices.STATUS_RESERVED), is_pool=True,
mark_utilized=True,
description='foobar1',
),
Prefix(
prefix='10.0.1.0/24',
tenant=tenants[0],
scope=sites[0],
vrf=vrfs[0],
vlan=vlans[0],
role=roles[0],
description='foobar2',
),
Prefix(
prefix='10.0.2.0/24',
tenant=tenants[1],
scope=sites[1],
vrf=vrfs[1],
vlan=vlans[1],
role=roles[1],
status=PrefixStatusChoices.STATUS_DEPRECATED,
),
Prefix(
prefix='10.0.3.0/24',
tenant=tenants[2],
scope=sites[2],
vrf=vrfs[2],
vlan=vlans[2],
role=roles[2],
status=PrefixStatusChoices.STATUS_RESERVED,
),
Prefix(
prefix='2001:db8::/64',
tenant=None,
scope=None,
vrf=None,
vlan=None,
role=None,
is_pool=True,
mark_utilized=True,
),
Prefix(
prefix='2001:db8:0:1::/64',
tenant=tenants[0],
scope=sites[0],
vrf=vrfs[0],
vlan=vlans[0],
role=roles[0]
),
Prefix(
prefix='2001:db8:0:2::/64',
tenant=tenants[1],
scope=sites[1],
vrf=vrfs[1],
vlan=vlans[1],
role=roles[1],
status=PrefixStatusChoices.STATUS_DEPRECATED,
),
Prefix(
prefix='2001:db8:0:3::/64',
tenant=tenants[2],
scope=sites[2],
vrf=vrfs[2],
vlan=vlans[2],
role=roles[2],
status=PrefixStatusChoices.STATUS_RESERVED,
),
Prefix(prefix='10.0.0.0/16'), Prefix(prefix='10.0.0.0/16'),
Prefix(prefix='2001:db8::/32'), Prefix(prefix='2001:db8::/32'),
) )
@ -1365,7 +1435,10 @@ class FHRPGroupTestCase(TestCase, ChangeLoggedFilterSetTests):
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_auth_type(self): def test_auth_type(self):
params = {'auth_type': [FHRPGroupAuthTypeChoices.AUTHENTICATION_PLAINTEXT, FHRPGroupAuthTypeChoices.AUTHENTICATION_MD5]} params = {'auth_type': [
FHRPGroupAuthTypeChoices.AUTHENTICATION_PLAINTEXT,
FHRPGroupAuthTypeChoices.AUTHENTICATION_MD5,
]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_auth_key(self): def test_auth_key(self):
@ -1653,9 +1726,15 @@ class VLANTestCase(TestCase, ChangeLoggedFilterSetTests):
device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1') device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1')
role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1') role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1')
devices = ( devices = (
Device(name='Device 1', site=sites[0], location=locations[0], rack=racks[0], device_type=device_type, role=role), Device(
Device(name='Device 2', site=sites[1], location=locations[1], rack=racks[1], device_type=device_type, role=role), name='Device 1', site=sites[0], location=locations[0], rack=racks[0], device_type=device_type, role=role
Device(name='Device 3', site=sites[2], location=locations[2], rack=racks[2], device_type=device_type, role=role), ),
Device(
name='Device 2', site=sites[1], location=locations[1], rack=racks[1], device_type=device_type, role=role
),
Device(
name='Device 3', site=sites[2], location=locations[2], rack=racks[2], device_type=device_type, role=role
),
) )
Device.objects.bulk_create(devices) Device.objects.bulk_create(devices)
@ -1773,20 +1852,64 @@ class VLANTestCase(TestCase, ChangeLoggedFilterSetTests):
VLAN(vid=19, name='Cluster 1', group=groups[18]), VLAN(vid=19, name='Cluster 1', group=groups[18]),
VLAN(vid=20, name='Cluster 2', group=groups[19]), VLAN(vid=20, name='Cluster 2', group=groups[19]),
VLAN(vid=21, name='Cluster 3', group=groups[20]), VLAN(vid=21, name='Cluster 3', group=groups[20]),
VLAN(
VLAN(vid=101, name='VLAN 101', site=sites[3], group=groups[21], role=roles[0], tenant=tenants[0], status=VLANStatusChoices.STATUS_ACTIVE), vid=101,
VLAN(vid=102, name='VLAN 102', site=sites[3], group=groups[21], role=roles[0], tenant=tenants[0], status=VLANStatusChoices.STATUS_ACTIVE), name='VLAN 101',
VLAN(vid=201, name='VLAN 201', site=sites[4], group=groups[22], role=roles[1], tenant=tenants[1], status=VLANStatusChoices.STATUS_DEPRECATED), site=sites[3],
VLAN(vid=202, name='VLAN 202', site=sites[4], group=groups[22], role=roles[1], tenant=tenants[1], status=VLANStatusChoices.STATUS_DEPRECATED), group=groups[21],
VLAN(vid=301, name='VLAN 301', site=sites[5], group=groups[23], role=roles[2], tenant=tenants[2], status=VLANStatusChoices.STATUS_RESERVED), role=roles[0],
VLAN(vid=302, name='VLAN 302', site=sites[5], group=groups[23], role=roles[2], tenant=tenants[2], status=VLANStatusChoices.STATUS_RESERVED), tenant=tenants[0],
status=VLANStatusChoices.STATUS_ACTIVE,
),
VLAN(
vid=102,
name='VLAN 102',
site=sites[3],
group=groups[21],
role=roles[0],
tenant=tenants[0],
status=VLANStatusChoices.STATUS_ACTIVE,
),
VLAN(
vid=201,
name='VLAN 201',
site=sites[4],
group=groups[22],
role=roles[1],
tenant=tenants[1],
status=VLANStatusChoices.STATUS_DEPRECATED,
),
VLAN(
vid=202,
name='VLAN 202',
site=sites[4],
group=groups[22],
role=roles[1],
tenant=tenants[1],
status=VLANStatusChoices.STATUS_DEPRECATED,
),
VLAN(
vid=301,
name='VLAN 301',
site=sites[5],
group=groups[23],
role=roles[2],
tenant=tenants[2],
status=VLANStatusChoices.STATUS_RESERVED,
),
VLAN(
vid=302,
name='VLAN 302',
site=sites[5],
group=groups[23],
role=roles[2],
tenant=tenants[2],
status=VLANStatusChoices.STATUS_RESERVED,
),
# Create one globally available VLAN on a VLAN group # Create one globally available VLAN on a VLAN group
VLAN(vid=500, name='VLAN Group 1', group=groups[24]), VLAN(vid=500, name='VLAN Group 1', group=groups[24]),
# Create one globally available VLAN # Create one globally available VLAN
VLAN(vid=1000, name='Global VLAN'), VLAN(vid=1000, name='Global VLAN'),
# Create some Q-in-Q service VLANs # Create some Q-in-Q service VLANs
VLAN(vid=2001, name='SVLAN 1', site=sites[6], qinq_role=VLANQinQRoleChoices.ROLE_SERVICE), VLAN(vid=2001, name='SVLAN 1', site=sites[6], qinq_role=VLANQinQRoleChoices.ROLE_SERVICE),
VLAN(vid=2002, name='SVLAN 2', site=sites[6], qinq_role=VLANQinQRoleChoices.ROLE_SERVICE), VLAN(vid=2002, name='SVLAN 2', site=sites[6], qinq_role=VLANQinQRoleChoices.ROLE_SERVICE),
@ -1795,11 +1918,31 @@ class VLANTestCase(TestCase, ChangeLoggedFilterSetTests):
VLAN.objects.bulk_create(vlans) VLAN.objects.bulk_create(vlans)
# Create Q-in-Q customer VLANs # Create Q-in-Q customer VLANs
VLAN.objects.bulk_create([ VLAN.objects.bulk_create(
VLAN(vid=3001, name='CVLAN 1', site=sites[6], qinq_svlan=vlans[29], qinq_role=VLANQinQRoleChoices.ROLE_CUSTOMER), [
VLAN(vid=3002, name='CVLAN 2', site=sites[6], qinq_svlan=vlans[30], qinq_role=VLANQinQRoleChoices.ROLE_CUSTOMER), VLAN(
VLAN(vid=3003, name='CVLAN 3', site=sites[6], qinq_svlan=vlans[31], qinq_role=VLANQinQRoleChoices.ROLE_CUSTOMER), vid=3001,
]) name='CVLAN 1',
site=sites[6],
qinq_svlan=vlans[29],
qinq_role=VLANQinQRoleChoices.ROLE_CUSTOMER,
),
VLAN(
vid=3002,
name='CVLAN 2',
site=sites[6],
qinq_svlan=vlans[30],
qinq_role=VLANQinQRoleChoices.ROLE_CUSTOMER,
),
VLAN(
vid=3003,
name='CVLAN 3',
site=sites[6],
qinq_svlan=vlans[31],
qinq_role=VLANQinQRoleChoices.ROLE_CUSTOMER,
),
]
)
# Assign VLANs to device interfaces # Assign VLANs to device interfaces
interfaces[0].untagged_vlan = vlans[0] interfaces[0].untagged_vlan = vlans[0]
@ -2125,12 +2268,39 @@ class ServiceTestCase(TestCase, ChangeLoggedFilterSetTests):
VirtualMachine.objects.bulk_create(virtual_machines) VirtualMachine.objects.bulk_create(virtual_machines)
services = ( services = (
Service(device=devices[0], name='Service 1', protocol=ServiceProtocolChoices.PROTOCOL_TCP, ports=[1001], description='foobar1'), Service(
Service(device=devices[1], name='Service 2', protocol=ServiceProtocolChoices.PROTOCOL_TCP, ports=[1002], description='foobar2'), device=devices[0],
name='Service 1',
protocol=ServiceProtocolChoices.PROTOCOL_TCP,
ports=[1001],
description='foobar1',
),
Service(
device=devices[1],
name='Service 2',
protocol=ServiceProtocolChoices.PROTOCOL_TCP,
ports=[1002],
description='foobar2',
),
Service(device=devices[2], name='Service 3', protocol=ServiceProtocolChoices.PROTOCOL_UDP, ports=[1003]), Service(device=devices[2], name='Service 3', protocol=ServiceProtocolChoices.PROTOCOL_UDP, ports=[1003]),
Service(virtual_machine=virtual_machines[0], name='Service 4', protocol=ServiceProtocolChoices.PROTOCOL_TCP, ports=[2001]), Service(
Service(virtual_machine=virtual_machines[1], name='Service 5', protocol=ServiceProtocolChoices.PROTOCOL_TCP, ports=[2002]), virtual_machine=virtual_machines[0],
Service(virtual_machine=virtual_machines[2], name='Service 6', protocol=ServiceProtocolChoices.PROTOCOL_UDP, ports=[2003]), name='Service 4',
protocol=ServiceProtocolChoices.PROTOCOL_TCP,
ports=[2001],
),
Service(
virtual_machine=virtual_machines[1],
name='Service 5',
protocol=ServiceProtocolChoices.PROTOCOL_TCP,
ports=[2002],
),
Service(
virtual_machine=virtual_machines[2],
name='Service 6',
protocol=ServiceProtocolChoices.PROTOCOL_UDP,
ports=[2003],
),
) )
Service.objects.bulk_create(services) Service.objects.bulk_create(services)
services[0].ipaddresses.add(ip_addresses[0]) services[0].ipaddresses.add(ip_addresses[0])

View File

@ -39,29 +39,50 @@ class TestAggregate(TestCase):
class TestIPRange(TestCase): class TestIPRange(TestCase):
def test_overlapping_range(self): def test_overlapping_range(self):
iprange_192_168 = IPRange.objects.create(start_address=IPNetwork('192.168.0.1/22'), end_address=IPNetwork('192.168.0.49/22')) iprange_192_168 = IPRange.objects.create(
start_address=IPNetwork('192.168.0.1/22'), end_address=IPNetwork('192.168.0.49/22')
)
iprange_192_168.clean() iprange_192_168.clean()
iprange_3_1_99 = IPRange.objects.create(start_address=IPNetwork('1.2.3.1/24'), end_address=IPNetwork('1.2.3.99/24')) iprange_3_1_99 = IPRange.objects.create(
start_address=IPNetwork('1.2.3.1/24'), end_address=IPNetwork('1.2.3.99/24')
)
iprange_3_1_99.clean() iprange_3_1_99.clean()
iprange_3_100_199 = IPRange.objects.create(start_address=IPNetwork('1.2.3.100/24'), end_address=IPNetwork('1.2.3.199/24')) iprange_3_100_199 = IPRange.objects.create(
start_address=IPNetwork('1.2.3.100/24'), end_address=IPNetwork('1.2.3.199/24')
)
iprange_3_100_199.clean() iprange_3_100_199.clean()
iprange_3_200_255 = IPRange.objects.create(start_address=IPNetwork('1.2.3.200/24'), end_address=IPNetwork('1.2.3.255/24')) iprange_3_200_255 = IPRange.objects.create(
start_address=IPNetwork('1.2.3.200/24'), end_address=IPNetwork('1.2.3.255/24')
)
iprange_3_200_255.clean() iprange_3_200_255.clean()
iprange_4_1_99 = IPRange.objects.create(start_address=IPNetwork('1.2.4.1/24'), end_address=IPNetwork('1.2.4.99/24')) iprange_4_1_99 = IPRange.objects.create(
start_address=IPNetwork('1.2.4.1/24'), end_address=IPNetwork('1.2.4.99/24')
)
iprange_4_1_99.clean() iprange_4_1_99.clean()
iprange_4_200 = IPRange.objects.create(start_address=IPNetwork('1.2.4.200/24'), end_address=IPNetwork('1.2.4.255/24')) iprange_4_200 = IPRange.objects.create(
start_address=IPNetwork('1.2.4.200/24'), end_address=IPNetwork('1.2.4.255/24')
)
iprange_4_200.clean() iprange_4_200.clean()
# Overlapping range entirely within existing # Overlapping range entirely within existing
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
iprange_3_123_124 = IPRange.objects.create(start_address=IPNetwork('1.2.3.123/26'), end_address=IPNetwork('1.2.3.124/26')) iprange_3_123_124 = IPRange.objects.create(
start_address=IPNetwork('1.2.3.123/26'), end_address=IPNetwork('1.2.3.124/26')
)
iprange_3_123_124.clean() iprange_3_123_124.clean()
# Overlapping range starting within existing # Overlapping range starting within existing
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
iprange_4_98_101 = IPRange.objects.create(start_address=IPNetwork('1.2.4.98/24'), end_address=IPNetwork('1.2.4.101/24')) iprange_4_98_101 = IPRange.objects.create(
start_address=IPNetwork('1.2.4.98/24'), end_address=IPNetwork('1.2.4.101/24')
)
iprange_4_98_101.clean() iprange_4_98_101.clean()
# Overlapping range ending within existing # Overlapping range ending within existing
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
iprange_4_198_201 = IPRange.objects.create(start_address=IPNetwork('1.2.4.198/24'), end_address=IPNetwork('1.2.4.201/24')) iprange_4_198_201 = IPRange.objects.create(
start_address=IPNetwork('1.2.4.198/24'), end_address=IPNetwork('1.2.4.201/24')
)
iprange_4_198_201.clean() iprange_4_198_201.clean()
@ -105,13 +126,30 @@ class TestPrefix(TestCase):
def test_get_child_ranges(self): def test_get_child_ranges(self):
prefix = Prefix(prefix='192.168.0.16/28') prefix = Prefix(prefix='192.168.0.16/28')
prefix.save() prefix.save()
ranges = IPRange.objects.bulk_create(( ranges = IPRange.objects.bulk_create(
IPRange(start_address=IPNetwork('192.168.0.1/24'), end_address=IPNetwork('192.168.0.10/24'), size=10), # No overlap (
IPRange(start_address=IPNetwork('192.168.0.11/24'), end_address=IPNetwork('192.168.0.17/24'), size=7), # Partial overlap # No overlap
IPRange(start_address=IPNetwork('192.168.0.18/24'), end_address=IPNetwork('192.168.0.23/24'), size=6), # Full overlap IPRange(
IPRange(start_address=IPNetwork('192.168.0.24/24'), end_address=IPNetwork('192.168.0.30/24'), size=7), # Full overlap start_address=IPNetwork('192.168.0.1/24'), end_address=IPNetwork('192.168.0.10/24'), size=10
IPRange(start_address=IPNetwork('192.168.0.31/24'), end_address=IPNetwork('192.168.0.40/24'), size=10), # Partial overlap ),
)) # Partial overlap
IPRange(
start_address=IPNetwork('192.168.0.11/24'), end_address=IPNetwork('192.168.0.17/24'), size=7
),
# Full overlap
IPRange(
start_address=IPNetwork('192.168.0.18/24'), end_address=IPNetwork('192.168.0.23/24'), size=6
),
# Full overlap
IPRange(
start_address=IPNetwork('192.168.0.24/24'), end_address=IPNetwork('192.168.0.30/24'), size=7
),
# Partial overlap
IPRange(
start_address=IPNetwork('192.168.0.31/24'), end_address=IPNetwork('192.168.0.40/24'), size=10
),
)
)
child_ranges = prefix.get_child_ranges() child_ranges = prefix.get_child_ranges()

View File

@ -92,8 +92,8 @@ class PrefixOrderingTestCase(OrderingTestBase):
def test_prefix_complex_ordering(self): def test_prefix_complex_ordering(self):
""" """
This function tests a complex ordering of interwoven prefixes and vrfs. This is the current expected ordering of VRFs This function tests a complex ordering of interwoven prefixes and vrfs. This is the current expected ordering
This includes the testing of the Container status. of VRFs. This includes the testing of the Container status.
The proper ordering, to get proper containerization should be: The proper ordering, to get proper containerization should be:
None:10.0.0.0/8 None:10.0.0.0/8
@ -125,7 +125,6 @@ class PrefixOrderingTestCase(OrderingTestBase):
class IPAddressOrderingTestCase(OrderingTestBase): class IPAddressOrderingTestCase(OrderingTestBase):
def test_address_vrf_ordering(self): def test_address_vrf_ordering(self):
""" """
This function tests ordering with the inclusion of vrfs This function tests ordering with the inclusion of vrfs
@ -147,24 +146,54 @@ class IPAddressOrderingTestCase(OrderingTestBase):
IPAddress(status=IPAddressStatusChoices.STATUS_ACTIVE, vrf=vrf1, address=netaddr.IPNetwork('10.2.2.1/24')), IPAddress(status=IPAddressStatusChoices.STATUS_ACTIVE, vrf=vrf1, address=netaddr.IPNetwork('10.2.2.1/24')),
IPAddress(status=IPAddressStatusChoices.STATUS_ACTIVE, vrf=vrf1, address=netaddr.IPNetwork('10.2.3.1/24')), IPAddress(status=IPAddressStatusChoices.STATUS_ACTIVE, vrf=vrf1, address=netaddr.IPNetwork('10.2.3.1/24')),
IPAddress(status=IPAddressStatusChoices.STATUS_ACTIVE, vrf=vrf1, address=netaddr.IPNetwork('10.2.4.1/24')), IPAddress(status=IPAddressStatusChoices.STATUS_ACTIVE, vrf=vrf1, address=netaddr.IPNetwork('10.2.4.1/24')),
IPAddress(
IPAddress(status=IPAddressStatusChoices.STATUS_ACTIVE, vrf=vrf2, address=netaddr.IPNetwork('172.16.0.1/24')), status=IPAddressStatusChoices.STATUS_ACTIVE, vrf=vrf2, address=netaddr.IPNetwork('172.16.0.1/24')
IPAddress(status=IPAddressStatusChoices.STATUS_ACTIVE, vrf=vrf2, address=netaddr.IPNetwork('172.16.1.1/24')), ),
IPAddress(status=IPAddressStatusChoices.STATUS_ACTIVE, vrf=vrf2, address=netaddr.IPNetwork('172.16.2.1/24')), IPAddress(
IPAddress(status=IPAddressStatusChoices.STATUS_ACTIVE, vrf=vrf2, address=netaddr.IPNetwork('172.16.3.1/24')), status=IPAddressStatusChoices.STATUS_ACTIVE, vrf=vrf2, address=netaddr.IPNetwork('172.16.1.1/24')
IPAddress(status=IPAddressStatusChoices.STATUS_ACTIVE, vrf=vrf2, address=netaddr.IPNetwork('172.16.4.1/24')), ),
IPAddress(status=IPAddressStatusChoices.STATUS_ACTIVE, vrf=vrf2, address=netaddr.IPNetwork('172.17.0.1/24')), IPAddress(
IPAddress(status=IPAddressStatusChoices.STATUS_ACTIVE, vrf=vrf2, address=netaddr.IPNetwork('172.17.1.1/24')), status=IPAddressStatusChoices.STATUS_ACTIVE, vrf=vrf2, address=netaddr.IPNetwork('172.16.2.1/24')
IPAddress(status=IPAddressStatusChoices.STATUS_ACTIVE, vrf=vrf2, address=netaddr.IPNetwork('172.17.2.1/24')), ),
IPAddress(status=IPAddressStatusChoices.STATUS_ACTIVE, vrf=vrf2, address=netaddr.IPNetwork('172.17.3.1/24')), IPAddress(
IPAddress(status=IPAddressStatusChoices.STATUS_ACTIVE, vrf=vrf2, address=netaddr.IPNetwork('172.17.4.1/24')), status=IPAddressStatusChoices.STATUS_ACTIVE, vrf=vrf2, address=netaddr.IPNetwork('172.16.3.1/24')
),
IPAddress(status=IPAddressStatusChoices.STATUS_ACTIVE, vrf=None, address=netaddr.IPNetwork('192.168.0.1/24')), IPAddress(
IPAddress(status=IPAddressStatusChoices.STATUS_ACTIVE, vrf=None, address=netaddr.IPNetwork('192.168.1.1/24')), status=IPAddressStatusChoices.STATUS_ACTIVE, vrf=vrf2, address=netaddr.IPNetwork('172.16.4.1/24')
IPAddress(status=IPAddressStatusChoices.STATUS_ACTIVE, vrf=None, address=netaddr.IPNetwork('192.168.2.1/24')), ),
IPAddress(status=IPAddressStatusChoices.STATUS_ACTIVE, vrf=None, address=netaddr.IPNetwork('192.168.3.1/24')), IPAddress(
IPAddress(status=IPAddressStatusChoices.STATUS_ACTIVE, vrf=None, address=netaddr.IPNetwork('192.168.4.1/24')), status=IPAddressStatusChoices.STATUS_ACTIVE, vrf=vrf2, address=netaddr.IPNetwork('172.17.0.1/24')
IPAddress(status=IPAddressStatusChoices.STATUS_ACTIVE, vrf=None, address=netaddr.IPNetwork('192.168.5.1/24')), ),
IPAddress(
status=IPAddressStatusChoices.STATUS_ACTIVE, vrf=vrf2, address=netaddr.IPNetwork('172.17.1.1/24')
),
IPAddress(
status=IPAddressStatusChoices.STATUS_ACTIVE, vrf=vrf2, address=netaddr.IPNetwork('172.17.2.1/24')
),
IPAddress(
status=IPAddressStatusChoices.STATUS_ACTIVE, vrf=vrf2, address=netaddr.IPNetwork('172.17.3.1/24')
),
IPAddress(
status=IPAddressStatusChoices.STATUS_ACTIVE, vrf=vrf2, address=netaddr.IPNetwork('172.17.4.1/24')
),
IPAddress(
status=IPAddressStatusChoices.STATUS_ACTIVE, vrf=None, address=netaddr.IPNetwork('192.168.0.1/24')
),
IPAddress(
status=IPAddressStatusChoices.STATUS_ACTIVE, vrf=None, address=netaddr.IPNetwork('192.168.1.1/24')
),
IPAddress(
status=IPAddressStatusChoices.STATUS_ACTIVE, vrf=None, address=netaddr.IPNetwork('192.168.2.1/24')
),
IPAddress(
status=IPAddressStatusChoices.STATUS_ACTIVE, vrf=None, address=netaddr.IPNetwork('192.168.3.1/24')
),
IPAddress(
status=IPAddressStatusChoices.STATUS_ACTIVE, vrf=None, address=netaddr.IPNetwork('192.168.4.1/24')
),
IPAddress(
status=IPAddressStatusChoices.STATUS_ACTIVE, vrf=None, address=netaddr.IPNetwork('192.168.5.1/24')
),
) )
IPAddress.objects.bulk_create(addresses) IPAddress.objects.bulk_create(addresses)

View File

@ -707,11 +707,23 @@ class FHRPGroupTestCase(ViewTestCases.PrimaryObjectViewTestCase):
@classmethod @classmethod
def setUpTestData(cls): def setUpTestData(cls):
fhrp_groups = ( fhrp_groups = (
FHRPGroup(protocol=FHRPGroupProtocolChoices.PROTOCOL_VRRP2, group_id=10, auth_type=FHRPGroupAuthTypeChoices.AUTHENTICATION_PLAINTEXT, auth_key='foobar123'), FHRPGroup(
FHRPGroup(protocol=FHRPGroupProtocolChoices.PROTOCOL_VRRP3, group_id=20, auth_type=FHRPGroupAuthTypeChoices.AUTHENTICATION_MD5, auth_key='foobar123'), protocol=FHRPGroupProtocolChoices.PROTOCOL_VRRP2,
FHRPGroup(protocol=FHRPGroupProtocolChoices.PROTOCOL_HSRP, group_id=30), group_id=10,
auth_type=FHRPGroupAuthTypeChoices.AUTHENTICATION_PLAINTEXT,
auth_key='foobar123',
),
FHRPGroup(
protocol=FHRPGroupProtocolChoices.PROTOCOL_VRRP3,
group_id=20,
auth_type=FHRPGroupAuthTypeChoices.AUTHENTICATION_MD5,
auth_key='foobar123',
),
FHRPGroup(
protocol=FHRPGroupProtocolChoices.PROTOCOL_HSRP,
group_id=30
),
) )
FHRPGroup.objects.bulk_create(fhrp_groups) FHRPGroup.objects.bulk_create(fhrp_groups)

View File

@ -264,7 +264,9 @@ class ChangeLoggedModelFilterSet(BaseFilterSet):
action = { action = {
'created_by_request': Q(action=ObjectChangeActionChoices.ACTION_CREATE), 'created_by_request': Q(action=ObjectChangeActionChoices.ACTION_CREATE),
'updated_by_request': Q(action=ObjectChangeActionChoices.ACTION_UPDATE), 'updated_by_request': Q(action=ObjectChangeActionChoices.ACTION_UPDATE),
'modified_by_request': Q(action__in=[ObjectChangeActionChoices.ACTION_CREATE, ObjectChangeActionChoices.ACTION_UPDATE]), 'modified_by_request': Q(
action__in=[ObjectChangeActionChoices.ACTION_CREATE, ObjectChangeActionChoices.ACTION_UPDATE]
),
}.get(name) }.get(name)
request_id = value request_id = value
pks = ObjectChange.objects.filter( pks = ObjectChange.objects.filter(

View File

@ -995,7 +995,8 @@ class BulkComponentCreateView(GetReturnURLMixin, BaseMultiObjectView):
form.add_error(field, '{}: {}'.format(obj, ', '.join(e))) form.add_error(field, '{}: {}'.format(obj, ', '.join(e)))
# Enforce object-level permissions # Enforce object-level permissions
if self.queryset.filter(pk__in=[obj.pk for obj in new_components]).count() != len(new_components): component_ids = [obj.pk for obj in new_components]
if self.queryset.filter(pk__in=component_ids).count() != len(new_components):
raise PermissionsViolation raise PermissionsViolation
except IntegrityError: except IntegrityError:

View File

@ -143,7 +143,12 @@ class ObjectJobsView(ConditionalLoginRequiredMixin, View):
""" """
Render a list of all Job assigned to an object. For example: Render a list of all Job assigned to an object. For example:
path('data-sources/<int:pk>/jobs/', ObjectJobsView.as_view(), name='datasource_jobs', kwargs={'model': DataSource}), path(
'data-sources/<int:pk>/jobs/',
ObjectJobsView.as_view(),
name='datasource_jobs',
kwargs={'model': DataSource}
)
Attributes: Attributes:
base_template: The name of the template to extend. If not provided, "{app}/{model}.html" will be used. base_template: The name of the template to extend. If not provided, "{app}/{model}.html" will be used.