Compare commits

...

12 Commits

Author SHA1 Message Date
Daniel Sheppard
ef7880a013 Add test
Some checks failed
CI / build (20.x, 3.10) (push) Has been cancelled
CI / build (20.x, 3.11) (push) Has been cancelled
CI / build (20.x, 3.12) (push) Has been cancelled
2025-08-05 08:55:49 -05:00
Daniel Sheppard
8fd8493d11 Fix image path construction
Some checks failed
CI / build (20.x, 3.10) (push) Has been cancelled
CI / build (20.x, 3.11) (push) Has been cancelled
CI / build (20.x, 3.12) (push) Has been cancelled
2025-07-30 23:14:45 -05:00
Daniel Sheppard
db805053d9 Additional Unblackening
Some checks failed
CI / build (20.x, 3.10) (push) Has been cancelled
CI / build (20.x, 3.11) (push) Has been cancelled
CI / build (20.x, 3.12) (push) Has been cancelled
2025-07-28 10:00:43 -05:00
Daniel Sheppard
cf4db67e0b Unblacken code 2025-07-28 09:55:15 -05:00
Daniel Sheppard
f48e1cb534 Fixes: #19669 - Add an API endpoint to download image attachments 2025-07-24 14:16:02 -05:00
Jonathan Ramstedt
ffa9a52667 Closes #18936: add color name support for cable bulk import (#19949)
Some checks failed
CI / build (20.x, 3.10) (push) Has been cancelled
CI / build (20.x, 3.11) (push) Has been cancelled
CI / build (20.x, 3.12) (push) Has been cancelled
2025-07-24 09:54:49 -07:00
bctiemann
47320f9958 Merge pull request #19912 from miaow2/19903-regexp
Closes #19903: Add `regex` and `iregex` filter lookup expressions and corresponding tests
2025-07-24 12:32:19 -04:00
github-actions
d08a1bd07d Update source translation strings 2025-07-24 05:05:44 +00:00
Martin Hauser
14c4aeca54 Closes #19840 - Enable Site Filtering for Devices in Cable Bulk Import (#19923)
Some checks are pending
CI / build (20.x, 3.10) (push) Waiting to run
CI / build (20.x, 3.11) (push) Waiting to run
CI / build (20.x, 3.12) (push) Waiting to run
* feat(dcim): Add site fields to Cable bulk import form

Introduces `side_a_site` and `side_b_site` fields for the Cable bulk
import form. Limits device choices on both sides to the selected site
for improved input validation and consistency.

* feat(dcim): Enhance test data setup with multiple sites

Refactors tests to create multiple sites and assign devices accordingly.
Updates CSV data to include `side_a_site` and `side_b_site` fields for
scenarios involving multiple sites. This improves test coverage and
alignment with real-world use cases.

* docs(dcim): Update comments explaining indent for CSV import

Improved the inline comments to clarify the rationale behind allowing
devices with duplicate names on different sites during CSV bulk import.
2025-07-23 15:50:05 -05:00
Jason Novinger
26bec1275f Fixes #19934: add description field to Tenant bulk edit form (#19937) 2025-07-23 13:41:00 -07:00
Jason Novinger
fa2d7f6516 Fixes #19916: restore Rack device representation behavior
The select list of 'Images and Label', 'Images Only', and 'Label Only'
was broken during recent work while implementing #19823.

This fixes the issue by placing the `rack_elevation` class attribute on
the <div> element that contains the SVG after being loaded by HTMX. In
addition, we needed to slightly modify the selectors in the frontend
code that looked for the elements within the SVG to hide and/or show.
Previously, it was looking inside of a contentDocument embedded in an
<object> element. The simplified version just looks inside of the
SVG containing div.
2025-07-23 08:45:40 -04:00
Artem Kotik
c40bfb1445 Add regex and iregex filter lookup expressions and corresponding tests 2025-07-18 16:56:54 +02:00
14 changed files with 279 additions and 107 deletions

View File

@@ -80,18 +80,20 @@ GET /api/ipam/vlans/?vid__gt=900
String based (char) fields (Name, Address, etc) support these lookup expressions: String based (char) fields (Name, Address, etc) support these lookup expressions:
| Filter | Description | | Filter | Description |
|---------|----------------------------------------| |----------|----------------------------------------|
| `n` | Not equal to | | `n` | Not equal to |
| `ic` | Contains (case-insensitive) | | `ic` | Contains (case-insensitive) |
| `nic` | Does not contain (case-insensitive) | | `nic` | Does not contain (case-insensitive) |
| `isw` | Starts with (case-insensitive) | | `isw` | Starts with (case-insensitive) |
| `nisw` | Does not start with (case-insensitive) | | `nisw` | Does not start with (case-insensitive) |
| `iew` | Ends with (case-insensitive) | | `iew` | Ends with (case-insensitive) |
| `niew` | Does not end with (case-insensitive) | | `niew` | Does not end with (case-insensitive) |
| `ie` | Exact match (case-insensitive) | | `ie` | Exact match (case-insensitive) |
| `nie` | Inverse exact match (case-insensitive) | | `nie` | Inverse exact match (case-insensitive) |
| `empty` | Is empty/null (boolean) | | `empty` | Is empty/null (boolean) |
| `regex` | Regexp matching |
| `iregex` | Regexp matching (case-insensitive) |
Here is an example of a lookup expression on a string field that will return all devices with `switch` in the name: Here is an example of a lookup expression on a string field that will return all devices with `switch` in the name:

View File

@@ -1335,6 +1335,13 @@ class MACAddressImportForm(NetBoxModelImportForm):
class CableImportForm(NetBoxModelImportForm): class CableImportForm(NetBoxModelImportForm):
# Termination A # Termination A
side_a_site = CSVModelChoiceField(
label=_('Side A site'),
queryset=Site.objects.all(),
required=False,
to_field_name='name',
help_text=_('Site of parent device A (if any)'),
)
side_a_device = CSVModelChoiceField( side_a_device = CSVModelChoiceField(
label=_('Side A device'), label=_('Side A device'),
queryset=Device.objects.all(), queryset=Device.objects.all(),
@@ -1353,6 +1360,13 @@ class CableImportForm(NetBoxModelImportForm):
) )
# Termination B # Termination B
side_b_site = CSVModelChoiceField(
label=_('Side B site'),
queryset=Site.objects.all(),
required=False,
to_field_name='name',
help_text=_('Site of parent device B (if any)'),
)
side_b_device = CSVModelChoiceField( side_b_device = CSVModelChoiceField(
label=_('Side B device'), label=_('Side B device'),
queryset=Device.objects.all(), queryset=Device.objects.all(),
@@ -1396,14 +1410,39 @@ class CableImportForm(NetBoxModelImportForm):
required=False, required=False,
help_text=_('Length unit') help_text=_('Length unit')
) )
color = forms.CharField(
label=_('Color'),
required=False,
max_length=16,
help_text=_('Color name (e.g. "Red") or hex code (e.g. "f44336")')
)
class Meta: class Meta:
model = Cable model = Cable
fields = [ fields = [
'side_a_device', 'side_a_type', 'side_a_name', 'side_b_device', 'side_b_type', 'side_b_name', 'type', 'side_a_site', 'side_a_device', 'side_a_type', 'side_a_name', 'side_b_site', 'side_b_device', 'side_b_type',
'status', 'tenant', 'label', 'color', 'length', 'length_unit', 'description', 'comments', 'tags', 'side_b_name', 'type', 'status', 'tenant', 'label', 'color', 'length', 'length_unit', 'description',
'comments', 'tags',
] ]
def __init__(self, data=None, *args, **kwargs):
super().__init__(data, *args, **kwargs)
if data:
# Limit choices for side_a_device to the assigned side_a_site
if side_a_site := data.get('side_a_site'):
side_a_device_params = {f'site__{self.fields["side_a_site"].to_field_name}': side_a_site}
self.fields['side_a_device'].queryset = self.fields['side_a_device'].queryset.filter(
**side_a_device_params
)
# Limit choices for side_b_device to the assigned side_b_site
if side_b_site := data.get('side_b_site'):
side_b_device_params = {f'site__{self.fields["side_b_site"].to_field_name}': side_b_site}
self.fields['side_b_device'].queryset = self.fields['side_b_device'].queryset.filter(
**side_b_device_params
)
def _clean_side(self, side): def _clean_side(self, side):
""" """
Derive a Cable's A/B termination objects. Derive a Cable's A/B termination objects.
@@ -1440,6 +1479,24 @@ class CableImportForm(NetBoxModelImportForm):
setattr(self.instance, f'{side}_terminations', [termination_object]) setattr(self.instance, f'{side}_terminations', [termination_object])
return termination_object return termination_object
def _clean_color(self, color):
"""
Derive a colors hex code
:param color: color as hex or color name
"""
color_parsed = color.strip().lower()
for hex_code, label in ColorChoices.CHOICES:
if color.lower() == label.lower():
color_parsed = hex_code
if len(color_parsed) > 6:
raise forms.ValidationError(
_(f"{color} did not match any used color name and was longer than six characters: invalid hex.")
)
return color_parsed
def clean_side_a_name(self): def clean_side_a_name(self):
return self._clean_side('a') return self._clean_side('a')
@@ -1451,11 +1508,14 @@ class CableImportForm(NetBoxModelImportForm):
length_unit = self.cleaned_data.get('length_unit', None) length_unit = self.cleaned_data.get('length_unit', None)
return length_unit if length_unit is not None else '' return length_unit if length_unit is not None else ''
def clean_color(self):
color = self.cleaned_data.get('color', None)
return self._clean_color(color) if color is not None else ''
# #
# Virtual chassis # Virtual chassis
# #
class VirtualChassisImportForm(NetBoxModelImportForm): class VirtualChassisImportForm(NetBoxModelImportForm):
master = CSVModelChoiceField( master = CSVModelChoiceField(
label=_('Master'), label=_('Master'),

View File

@@ -3266,17 +3266,27 @@ class CableTestCase(
@classmethod @classmethod
def setUpTestData(cls): def setUpTestData(cls):
site = Site.objects.create(name='Site 1', slug='site-1') sites = (
Site(name='Site 1', slug='site-1'),
Site(name='Site 2', slug='site-2'),
)
Site.objects.bulk_create(sites)
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
devicetype = DeviceType.objects.create(model='Device Type 1', manufacturer=manufacturer) devicetype = DeviceType.objects.create(model='Device Type 1', manufacturer=manufacturer)
role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1') role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1')
vc = VirtualChassis.objects.create(name='Virtual Chassis') vc = VirtualChassis.objects.create(name='Virtual Chassis')
# NOTE: By design, NetBox now allows for the creation of devices with the same name if they belong to
# different sites.
# The CSV test below demonstrates that devices with identical names on different sites can be created
# and referenced successfully.
devices = ( devices = (
Device(name='Device 1', site=site, device_type=devicetype, role=role), # Create 'Device 1' assigned to 'Site 1'
Device(name='Device 2', site=site, device_type=devicetype, role=role), Device(name='Device 1', site=sites[0], device_type=devicetype, role=role),
Device(name='Device 3', site=site, device_type=devicetype, role=role), Device(name='Device 2', site=sites[0], device_type=devicetype, role=role),
Device(name='Device 4', site=site, device_type=devicetype, role=role), Device(name='Device 3', site=sites[0], device_type=devicetype, role=role),
# Create 'Device 1' assigned to 'Site 2' (allowed since the site is different)
Device(name='Device 1', site=sites[1], device_type=devicetype, role=role),
) )
Device.objects.bulk_create(devices) Device.objects.bulk_create(devices)
@@ -3327,13 +3337,15 @@ class CableTestCase(
'tags': [t.pk for t in tags], 'tags': [t.pk for t in tags],
} }
# Ensure that CSV bulk import supports assigning terminations from parent devices that share
# the same device name, provided those devices belong to different sites.
cls.csv_data = ( cls.csv_data = (
"side_a_device,side_a_type,side_a_name,side_b_device,side_b_type,side_b_name", "side_a_site,side_a_device,side_a_type,side_a_name,side_b_site,side_b_device,side_b_type,side_b_name",
"Device 3,dcim.interface,Interface 1,Device 4,dcim.interface,Interface 1", "Site 1,Device 3,dcim.interface,Interface 1,Site 2,Device 1,dcim.interface,Interface 1",
"Device 3,dcim.interface,Interface 2,Device 4,dcim.interface,Interface 2", "Site 1,Device 3,dcim.interface,Interface 2,Site 2,Device 1,dcim.interface,Interface 2",
"Device 3,dcim.interface,Interface 3,Device 4,dcim.interface,Interface 3", "Site 1,Device 3,dcim.interface,Interface 3,Site 2,Device 1,dcim.interface,Interface 3",
"Device 1,dcim.interface,Device 2 Interface,Device 4,dcim.interface,Interface 4", "Site 1,Device 1,dcim.interface,Device 2 Interface,Site 2,Device 1,dcim.interface,Interface 4",
"Device 1,dcim.interface,Device 3 Interface,Device 4,dcim.interface,Interface 5", "Site 1,Device 1,dcim.interface,Device 3 Interface,Site 2,Device 1,dcim.interface,Interface 5",
) )
cls.csv_update_data = ( cls.csv_update_data = (

View File

@@ -1,5 +1,7 @@
from django.conf import settings
from django.http import Http404 from django.http import Http404
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.views.static import serve
from django_rq.queues import get_connection from django_rq.queues import get_connection
from drf_spectacular.utils import extend_schema, extend_schema_view from drf_spectacular.utils import extend_schema, extend_schema_view
from rest_framework import status from rest_framework import status
@@ -200,6 +202,17 @@ class ImageAttachmentViewSet(NetBoxModelViewSet):
serializer_class = serializers.ImageAttachmentSerializer serializer_class = serializers.ImageAttachmentSerializer
filterset_class = filtersets.ImageAttachmentFilterSet filterset_class = filtersets.ImageAttachmentFilterSet
@action(
methods=['GET'],
detail=True,
url_path='download',
url_name='download',
)
def download(self, request, pk, *args, **kwargs):
obj = get_object_or_404(self.queryset, pk=pk)
# Render and return the elevation as an SVG drawing with the correct content type
return serve(request, obj.image.name, document_root=settings.MEDIA_ROOT)
# #
# Journal entries # Journal entries

View File

@@ -1,6 +1,8 @@
import datetime import datetime
from PIL import Image
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core.files.base import File
from django.urls import reverse from django.urls import reverse
from django.utils.timezone import make_aware, now from django.utils.timezone import make_aware, now
from rest_framework import status from rest_framework import status
@@ -616,6 +618,38 @@ class ImageAttachmentTest(
) )
ImageAttachment.objects.bulk_create(image_attachments) ImageAttachment.objects.bulk_create(image_attachments)
def test_image_download(self):
self.add_permissions('extras.view_imageattachment')
ct = ContentType.objects.get_for_model(Site)
site = Site.objects.get(name='Site 1', slug='site-1')
image = Image.new('RGB', size=(1, 1), color=(255, 0, 0))
image.save('test_image_download.png', format='PNG')
image_file = File(open('test_image_download.png', 'rb'))
content = image_file.read()
attachment = ImageAttachment(
object_type=ct,
object_id=site.pk,
name='Image Attachment 4',
image_height=1,
image_width=1
)
attachment.image.save('test_image_download.png', image_file, save=True)
attachment.save()
image = ImageAttachment.objects.get(name='Image Attachment 4')
url = reverse('extras-api:imageattachment-download', kwargs={'pk': image.pk})
response = self.client.get(url, **self.header)
downloaded_content = b''.join(response.streaming_content)
self.assertEqual(response.headers.get('Content-Type'), 'image/png')
self.assertEqual(response.headers.get('Content-Length'), '69')
self.assertEqual(
response.headers.get('Content-Disposition'), f'inline; filename="site_{site.pk}_Image_Attachment_4.png"'
)
self.assertEqual(content, downloaded_content)
class JournalEntryTest(APIViewTestCases.APIViewTestCase): class JournalEntryTest(APIViewTestCases.APIViewTestCase):
model = JournalEntry model = JournalEntry

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -35,7 +35,7 @@ function showRackElements(
selector: string, selector: string,
elevation: HTMLObjectElement, elevation: HTMLObjectElement,
): void { ): void {
const elements = elevation.contentDocument?.querySelectorAll(selector) ?? []; const elements = elevation.querySelectorAll(selector) ?? [];
for (const element of elements) { for (const element of elements) {
element.classList.remove('hidden'); element.classList.remove('hidden');
} }
@@ -45,7 +45,7 @@ function hideRackElements(
selector: string, selector: string,
elevation: HTMLObjectElement, elevation: HTMLObjectElement,
): void { ): void {
const elements = elevation.contentDocument?.querySelectorAll(selector) ?? []; const elements = elevation.querySelectorAll(selector) ?? [];
for (const element of elements) { for (const element of elements) {
element.classList.add('hidden'); element.classList.add('hidden');
} }

View File

@@ -1,5 +1,5 @@
{% load i18n %} {% load i18n %}
<div style="margin-left: -30px"> <div style="margin-left: -30px" class="rack_elevation">
<div <div
hx-get="{% url 'dcim-api:rack-elevation' pk=object.pk %}?face={{ face }}&render=svg{% if extra_params %}&{{ extra_params }}{% endif %}" hx-get="{% url 'dcim-api:rack-elevation' pk=object.pk %}?face={{ face }}&render=svg{% if extra_params %}&{{ extra_params }}{% endif %}"
hx-trigger="intersect" hx-trigger="intersect"

View File

@@ -45,12 +45,17 @@ class TenantBulkEditForm(NetBoxModelBulkEditForm):
queryset=TenantGroup.objects.all(), queryset=TenantGroup.objects.all(),
required=False required=False
) )
description = forms.CharField(
label=_('Description'),
max_length=200,
required=False
)
model = Tenant model = Tenant
fieldsets = ( fieldsets = (
FieldSet('group'), FieldSet('group', 'description'),
) )
nullable_fields = ('group',) nullable_fields = ('group', 'description')
# #

View File

@@ -98,6 +98,7 @@ class TenantTestCase(ViewTestCases.PrimaryObjectViewTestCase):
cls.bulk_edit_data = { cls.bulk_edit_data = {
'group': tenant_groups[1].pk, 'group': tenant_groups[1].pk,
'description': 'Bulk edit description',
} }

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-07-16 05:05+0000\n" "POT-Creation-Date: 2025-07-24 05:05+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -215,8 +215,8 @@ msgstr ""
#: netbox/dcim/forms/bulk_edit.py:344 netbox/dcim/forms/bulk_edit.py:730 #: netbox/dcim/forms/bulk_edit.py:344 netbox/dcim/forms/bulk_edit.py:730
#: netbox/dcim/forms/bulk_edit.py:935 netbox/dcim/forms/bulk_import.py:134 #: netbox/dcim/forms/bulk_edit.py:935 netbox/dcim/forms/bulk_import.py:134
#: netbox/dcim/forms/bulk_import.py:236 netbox/dcim/forms/bulk_import.py:337 #: netbox/dcim/forms/bulk_import.py:236 netbox/dcim/forms/bulk_import.py:337
#: netbox/dcim/forms/bulk_import.py:598 netbox/dcim/forms/bulk_import.py:1479 #: netbox/dcim/forms/bulk_import.py:598 netbox/dcim/forms/bulk_import.py:1512
#: netbox/dcim/forms/bulk_import.py:1507 netbox/dcim/forms/filtersets.py:89 #: netbox/dcim/forms/bulk_import.py:1540 netbox/dcim/forms/filtersets.py:89
#: netbox/dcim/forms/filtersets.py:227 netbox/dcim/forms/filtersets.py:344 #: netbox/dcim/forms/filtersets.py:227 netbox/dcim/forms/filtersets.py:344
#: netbox/dcim/forms/filtersets.py:441 netbox/dcim/forms/filtersets.py:773 #: netbox/dcim/forms/filtersets.py:441 netbox/dcim/forms/filtersets.py:773
#: netbox/dcim/forms/filtersets.py:992 netbox/dcim/forms/filtersets.py:1065 #: netbox/dcim/forms/filtersets.py:992 netbox/dcim/forms/filtersets.py:1065
@@ -587,9 +587,10 @@ msgstr ""
#: netbox/templates/wireless/wirelesslan.html:34 #: netbox/templates/wireless/wirelesslan.html:34
#: netbox/templates/wireless/wirelesslangroup.html:33 #: netbox/templates/wireless/wirelesslangroup.html:33
#: netbox/templates/wireless/wirelesslink.html:34 #: netbox/templates/wireless/wirelesslink.html:34
#: netbox/tenancy/forms/bulk_edit.py:32 netbox/tenancy/forms/bulk_edit.py:82 #: netbox/tenancy/forms/bulk_edit.py:32 netbox/tenancy/forms/bulk_edit.py:49
#: netbox/tenancy/forms/bulk_edit.py:130 netbox/users/forms/bulk_edit.py:64 #: netbox/tenancy/forms/bulk_edit.py:87 netbox/tenancy/forms/bulk_edit.py:135
#: netbox/users/forms/bulk_edit.py:82 netbox/users/forms/bulk_edit.py:112 #: netbox/users/forms/bulk_edit.py:64 netbox/users/forms/bulk_edit.py:82
#: netbox/users/forms/bulk_edit.py:112
#: netbox/virtualization/forms/bulk_edit.py:33 #: netbox/virtualization/forms/bulk_edit.py:33
#: netbox/virtualization/forms/bulk_edit.py:47 #: netbox/virtualization/forms/bulk_edit.py:47
#: netbox/virtualization/forms/bulk_edit.py:82 #: netbox/virtualization/forms/bulk_edit.py:82
@@ -692,8 +693,8 @@ msgstr ""
#: netbox/dcim/forms/bulk_import.py:766 netbox/dcim/forms/bulk_import.py:792 #: netbox/dcim/forms/bulk_import.py:766 netbox/dcim/forms/bulk_import.py:792
#: netbox/dcim/forms/bulk_import.py:818 netbox/dcim/forms/bulk_import.py:838 #: netbox/dcim/forms/bulk_import.py:818 netbox/dcim/forms/bulk_import.py:838
#: netbox/dcim/forms/bulk_import.py:924 netbox/dcim/forms/bulk_import.py:1018 #: netbox/dcim/forms/bulk_import.py:924 netbox/dcim/forms/bulk_import.py:1018
#: netbox/dcim/forms/bulk_import.py:1060 netbox/dcim/forms/bulk_import.py:1381 #: netbox/dcim/forms/bulk_import.py:1060 netbox/dcim/forms/bulk_import.py:1395
#: netbox/dcim/forms/bulk_import.py:1544 netbox/dcim/forms/filtersets.py:1023 #: netbox/dcim/forms/bulk_import.py:1577 netbox/dcim/forms/filtersets.py:1023
#: netbox/dcim/forms/filtersets.py:1122 netbox/dcim/forms/filtersets.py:1243 #: netbox/dcim/forms/filtersets.py:1122 netbox/dcim/forms/filtersets.py:1243
#: netbox/dcim/forms/filtersets.py:1315 netbox/dcim/forms/filtersets.py:1340 #: netbox/dcim/forms/filtersets.py:1315 netbox/dcim/forms/filtersets.py:1340
#: netbox/dcim/forms/filtersets.py:1364 netbox/dcim/forms/filtersets.py:1384 #: netbox/dcim/forms/filtersets.py:1364 netbox/dcim/forms/filtersets.py:1384
@@ -763,8 +764,8 @@ msgstr ""
#: netbox/dcim/forms/bulk_edit.py:1819 netbox/dcim/forms/bulk_import.py:91 #: netbox/dcim/forms/bulk_edit.py:1819 netbox/dcim/forms/bulk_import.py:91
#: netbox/dcim/forms/bulk_import.py:150 netbox/dcim/forms/bulk_import.py:254 #: netbox/dcim/forms/bulk_import.py:150 netbox/dcim/forms/bulk_import.py:254
#: netbox/dcim/forms/bulk_import.py:563 netbox/dcim/forms/bulk_import.py:717 #: netbox/dcim/forms/bulk_import.py:563 netbox/dcim/forms/bulk_import.py:717
#: netbox/dcim/forms/bulk_import.py:1168 netbox/dcim/forms/bulk_import.py:1375 #: netbox/dcim/forms/bulk_import.py:1168 netbox/dcim/forms/bulk_import.py:1389
#: netbox/dcim/forms/bulk_import.py:1539 netbox/dcim/forms/bulk_import.py:1603 #: netbox/dcim/forms/bulk_import.py:1572 netbox/dcim/forms/bulk_import.py:1636
#: netbox/dcim/forms/filtersets.py:180 netbox/dcim/forms/filtersets.py:239 #: netbox/dcim/forms/filtersets.py:180 netbox/dcim/forms/filtersets.py:239
#: netbox/dcim/forms/filtersets.py:361 netbox/dcim/forms/filtersets.py:819 #: netbox/dcim/forms/filtersets.py:361 netbox/dcim/forms/filtersets.py:819
#: netbox/dcim/forms/filtersets.py:944 netbox/dcim/forms/filtersets.py:1026 #: netbox/dcim/forms/filtersets.py:944 netbox/dcim/forms/filtersets.py:1026
@@ -841,8 +842,8 @@ msgstr ""
#: netbox/dcim/forms/bulk_edit.py:856 netbox/dcim/forms/bulk_edit.py:1824 #: netbox/dcim/forms/bulk_edit.py:856 netbox/dcim/forms/bulk_edit.py:1824
#: netbox/dcim/forms/bulk_import.py:110 netbox/dcim/forms/bulk_import.py:155 #: netbox/dcim/forms/bulk_import.py:110 netbox/dcim/forms/bulk_import.py:155
#: netbox/dcim/forms/bulk_import.py:247 netbox/dcim/forms/bulk_import.py:362 #: netbox/dcim/forms/bulk_import.py:247 netbox/dcim/forms/bulk_import.py:362
#: netbox/dcim/forms/bulk_import.py:537 netbox/dcim/forms/bulk_import.py:1387 #: netbox/dcim/forms/bulk_import.py:537 netbox/dcim/forms/bulk_import.py:1401
#: netbox/dcim/forms/bulk_import.py:1596 netbox/dcim/forms/filtersets.py:175 #: netbox/dcim/forms/bulk_import.py:1629 netbox/dcim/forms/filtersets.py:175
#: netbox/dcim/forms/filtersets.py:207 netbox/dcim/forms/filtersets.py:325 #: netbox/dcim/forms/filtersets.py:207 netbox/dcim/forms/filtersets.py:325
#: netbox/dcim/forms/filtersets.py:401 netbox/dcim/forms/filtersets.py:422 #: netbox/dcim/forms/filtersets.py:401 netbox/dcim/forms/filtersets.py:422
#: netbox/dcim/forms/filtersets.py:742 netbox/dcim/forms/filtersets.py:936 #: netbox/dcim/forms/filtersets.py:742 netbox/dcim/forms/filtersets.py:936
@@ -1012,7 +1013,7 @@ msgstr ""
#: netbox/circuits/forms/bulk_edit.py:215 #: netbox/circuits/forms/bulk_edit.py:215
#: netbox/circuits/forms/model_forms.py:171 #: netbox/circuits/forms/model_forms.py:171
#: netbox/dcim/forms/bulk_import.py:1348 netbox/dcim/forms/bulk_import.py:1366 #: netbox/dcim/forms/bulk_import.py:1355 netbox/dcim/forms/bulk_import.py:1380
msgid "Termination type" msgid "Termination type"
msgstr "" msgstr ""
@@ -1065,7 +1066,7 @@ msgstr ""
#: netbox/templates/dcim/virtualchassis.html:68 #: netbox/templates/dcim/virtualchassis.html:68
#: netbox/templates/dcim/virtualchassis_edit.html:60 #: netbox/templates/dcim/virtualchassis_edit.html:60
#: netbox/templates/ipam/inc/panels/fhrp_groups.html:26 #: netbox/templates/ipam/inc/panels/fhrp_groups.html:26
#: netbox/tenancy/forms/bulk_edit.py:159 netbox/tenancy/forms/filtersets.py:110 #: netbox/tenancy/forms/bulk_edit.py:164 netbox/tenancy/forms/filtersets.py:110
msgid "Priority" msgid "Priority"
msgstr "" msgstr ""
@@ -1116,7 +1117,7 @@ msgstr ""
#: netbox/templates/virtualization/virtualmachine.html:23 #: netbox/templates/virtualization/virtualmachine.html:23
#: netbox/templates/vpn/tunneltermination.html:17 #: netbox/templates/vpn/tunneltermination.html:17
#: netbox/templates/wireless/inc/wirelesslink_interface.html:20 #: netbox/templates/wireless/inc/wirelesslink_interface.html:20
#: netbox/tenancy/forms/bulk_edit.py:154 netbox/tenancy/forms/filtersets.py:107 #: netbox/tenancy/forms/bulk_edit.py:159 netbox/tenancy/forms/filtersets.py:107
#: netbox/tenancy/forms/model_forms.py:139 #: netbox/tenancy/forms/model_forms.py:139
#: netbox/tenancy/tables/contacts.py:110 #: netbox/tenancy/tables/contacts.py:110
#: netbox/virtualization/forms/bulk_edit.py:127 #: netbox/virtualization/forms/bulk_edit.py:127
@@ -1148,7 +1149,7 @@ msgstr ""
#: netbox/circuits/forms/bulk_import.py:229 netbox/dcim/forms/bulk_import.py:93 #: netbox/circuits/forms/bulk_import.py:229 netbox/dcim/forms/bulk_import.py:93
#: netbox/dcim/forms/bulk_import.py:152 netbox/dcim/forms/bulk_import.py:256 #: netbox/dcim/forms/bulk_import.py:152 netbox/dcim/forms/bulk_import.py:256
#: netbox/dcim/forms/bulk_import.py:565 netbox/dcim/forms/bulk_import.py:719 #: netbox/dcim/forms/bulk_import.py:565 netbox/dcim/forms/bulk_import.py:719
#: netbox/dcim/forms/bulk_import.py:1170 netbox/dcim/forms/bulk_import.py:1541 #: netbox/dcim/forms/bulk_import.py:1170 netbox/dcim/forms/bulk_import.py:1574
#: netbox/ipam/forms/bulk_import.py:197 netbox/ipam/forms/bulk_import.py:265 #: netbox/ipam/forms/bulk_import.py:197 netbox/ipam/forms/bulk_import.py:265
#: netbox/ipam/forms/bulk_import.py:301 netbox/ipam/forms/bulk_import.py:498 #: netbox/ipam/forms/bulk_import.py:301 netbox/ipam/forms/bulk_import.py:498
#: netbox/ipam/forms/bulk_import.py:511 #: netbox/ipam/forms/bulk_import.py:511
@@ -1164,8 +1165,8 @@ msgstr ""
#: netbox/circuits/forms/bulk_import.py:236 #: netbox/circuits/forms/bulk_import.py:236
#: netbox/dcim/forms/bulk_import.py:114 netbox/dcim/forms/bulk_import.py:159 #: netbox/dcim/forms/bulk_import.py:114 netbox/dcim/forms/bulk_import.py:159
#: netbox/dcim/forms/bulk_import.py:366 netbox/dcim/forms/bulk_import.py:541 #: netbox/dcim/forms/bulk_import.py:366 netbox/dcim/forms/bulk_import.py:541
#: netbox/dcim/forms/bulk_import.py:1391 netbox/dcim/forms/bulk_import.py:1536 #: netbox/dcim/forms/bulk_import.py:1405 netbox/dcim/forms/bulk_import.py:1569
#: netbox/dcim/forms/bulk_import.py:1600 netbox/ipam/forms/bulk_import.py:45 #: netbox/dcim/forms/bulk_import.py:1633 netbox/ipam/forms/bulk_import.py:45
#: netbox/ipam/forms/bulk_import.py:74 netbox/ipam/forms/bulk_import.py:102 #: netbox/ipam/forms/bulk_import.py:74 netbox/ipam/forms/bulk_import.py:102
#: netbox/ipam/forms/bulk_import.py:122 netbox/ipam/forms/bulk_import.py:142 #: netbox/ipam/forms/bulk_import.py:122 netbox/ipam/forms/bulk_import.py:142
#: netbox/ipam/forms/bulk_import.py:171 netbox/ipam/forms/bulk_import.py:260 #: netbox/ipam/forms/bulk_import.py:171 netbox/ipam/forms/bulk_import.py:260
@@ -1245,8 +1246,8 @@ msgstr ""
#: netbox/dcim/forms/bulk_edit.py:466 netbox/dcim/forms/bulk_edit.py:735 #: netbox/dcim/forms/bulk_edit.py:466 netbox/dcim/forms/bulk_edit.py:735
#: netbox/dcim/forms/bulk_edit.py:790 netbox/dcim/forms/bulk_edit.py:944 #: netbox/dcim/forms/bulk_edit.py:790 netbox/dcim/forms/bulk_edit.py:944
#: netbox/dcim/forms/bulk_import.py:241 netbox/dcim/forms/bulk_import.py:343 #: netbox/dcim/forms/bulk_import.py:241 netbox/dcim/forms/bulk_import.py:343
#: netbox/dcim/forms/bulk_import.py:604 netbox/dcim/forms/bulk_import.py:1485 #: netbox/dcim/forms/bulk_import.py:604 netbox/dcim/forms/bulk_import.py:1518
#: netbox/dcim/forms/bulk_import.py:1519 netbox/dcim/forms/filtersets.py:97 #: netbox/dcim/forms/bulk_import.py:1552 netbox/dcim/forms/filtersets.py:97
#: netbox/dcim/forms/filtersets.py:324 netbox/dcim/forms/filtersets.py:358 #: netbox/dcim/forms/filtersets.py:324 netbox/dcim/forms/filtersets.py:358
#: netbox/dcim/forms/filtersets.py:398 netbox/dcim/forms/filtersets.py:449 #: netbox/dcim/forms/filtersets.py:398 netbox/dcim/forms/filtersets.py:449
#: netbox/dcim/forms/filtersets.py:739 netbox/dcim/forms/filtersets.py:782 #: netbox/dcim/forms/filtersets.py:739 netbox/dcim/forms/filtersets.py:782
@@ -1948,7 +1949,7 @@ msgstr ""
#: netbox/dcim/forms/bulk_import.py:1007 netbox/dcim/forms/bulk_import.py:1055 #: netbox/dcim/forms/bulk_import.py:1007 netbox/dcim/forms/bulk_import.py:1055
#: netbox/dcim/forms/bulk_import.py:1072 netbox/dcim/forms/bulk_import.py:1084 #: netbox/dcim/forms/bulk_import.py:1072 netbox/dcim/forms/bulk_import.py:1084
#: netbox/dcim/forms/bulk_import.py:1132 netbox/dcim/forms/bulk_import.py:1254 #: netbox/dcim/forms/bulk_import.py:1132 netbox/dcim/forms/bulk_import.py:1254
#: netbox/dcim/forms/bulk_import.py:1590 netbox/dcim/forms/connections.py:24 #: netbox/dcim/forms/bulk_import.py:1623 netbox/dcim/forms/connections.py:24
#: netbox/dcim/forms/filtersets.py:133 netbox/dcim/forms/filtersets.py:941 #: netbox/dcim/forms/filtersets.py:133 netbox/dcim/forms/filtersets.py:941
#: netbox/dcim/forms/filtersets.py:973 netbox/dcim/forms/filtersets.py:1119 #: netbox/dcim/forms/filtersets.py:973 netbox/dcim/forms/filtersets.py:1119
#: netbox/dcim/forms/filtersets.py:1310 netbox/dcim/forms/filtersets.py:1335 #: netbox/dcim/forms/filtersets.py:1310 netbox/dcim/forms/filtersets.py:1335
@@ -3106,7 +3107,7 @@ msgstr ""
#: netbox/templates/tenancy/tenantgroup.html:37 #: netbox/templates/tenancy/tenantgroup.html:37
#: netbox/templates/virtualization/vminterface.html:39 #: netbox/templates/virtualization/vminterface.html:39
#: netbox/templates/wireless/wirelesslangroup.html:37 #: netbox/templates/wireless/wirelesslangroup.html:37
#: netbox/tenancy/forms/bulk_edit.py:27 netbox/tenancy/forms/bulk_edit.py:62 #: netbox/tenancy/forms/bulk_edit.py:27 netbox/tenancy/forms/bulk_edit.py:67
#: netbox/tenancy/forms/bulk_import.py:24 #: netbox/tenancy/forms/bulk_import.py:24
#: netbox/tenancy/forms/bulk_import.py:58 #: netbox/tenancy/forms/bulk_import.py:58
#: netbox/tenancy/forms/model_forms.py:25 #: netbox/tenancy/forms/model_forms.py:25
@@ -4193,8 +4194,8 @@ msgstr ""
#: netbox/dcim/forms/bulk_edit.py:465 netbox/dcim/forms/bulk_edit.py:972 #: netbox/dcim/forms/bulk_edit.py:465 netbox/dcim/forms/bulk_edit.py:972
#: netbox/dcim/forms/bulk_import.py:350 netbox/dcim/forms/bulk_import.py:353 #: netbox/dcim/forms/bulk_import.py:350 netbox/dcim/forms/bulk_import.py:353
#: netbox/dcim/forms/bulk_import.py:611 netbox/dcim/forms/bulk_import.py:1526 #: netbox/dcim/forms/bulk_import.py:611 netbox/dcim/forms/bulk_import.py:1559
#: netbox/dcim/forms/bulk_import.py:1530 netbox/dcim/forms/filtersets.py:106 #: netbox/dcim/forms/bulk_import.py:1563 netbox/dcim/forms/filtersets.py:106
#: netbox/dcim/forms/filtersets.py:326 netbox/dcim/forms/filtersets.py:407 #: netbox/dcim/forms/filtersets.py:326 netbox/dcim/forms/filtersets.py:407
#: netbox/dcim/forms/filtersets.py:421 netbox/dcim/forms/filtersets.py:459 #: netbox/dcim/forms/filtersets.py:421 netbox/dcim/forms/filtersets.py:459
#: netbox/dcim/forms/filtersets.py:792 netbox/dcim/forms/filtersets.py:1005 #: netbox/dcim/forms/filtersets.py:792 netbox/dcim/forms/filtersets.py:1005
@@ -4394,8 +4395,8 @@ msgstr ""
msgid "Length" msgid "Length"
msgstr "" msgstr ""
#: netbox/dcim/forms/bulk_edit.py:875 netbox/dcim/forms/bulk_import.py:1394 #: netbox/dcim/forms/bulk_edit.py:875 netbox/dcim/forms/bulk_import.py:1408
#: netbox/dcim/forms/bulk_import.py:1397 netbox/dcim/forms/filtersets.py:1140 #: netbox/dcim/forms/bulk_import.py:1411 netbox/dcim/forms/filtersets.py:1140
msgid "Length unit" msgid "Length unit"
msgstr "" msgstr ""
@@ -4404,17 +4405,17 @@ msgstr ""
msgid "Domain" msgid "Domain"
msgstr "" msgstr ""
#: netbox/dcim/forms/bulk_edit.py:967 netbox/dcim/forms/bulk_import.py:1513 #: netbox/dcim/forms/bulk_edit.py:967 netbox/dcim/forms/bulk_import.py:1546
#: netbox/dcim/forms/filtersets.py:1226 netbox/dcim/forms/model_forms.py:855 #: netbox/dcim/forms/filtersets.py:1226 netbox/dcim/forms/model_forms.py:855
msgid "Power panel" msgid "Power panel"
msgstr "" msgstr ""
#: netbox/dcim/forms/bulk_edit.py:989 netbox/dcim/forms/bulk_import.py:1549 #: netbox/dcim/forms/bulk_edit.py:989 netbox/dcim/forms/bulk_import.py:1582
#: netbox/dcim/forms/filtersets.py:1248 netbox/templates/dcim/powerfeed.html:83 #: netbox/dcim/forms/filtersets.py:1248 netbox/templates/dcim/powerfeed.html:83
msgid "Supply" msgid "Supply"
msgstr "" msgstr ""
#: netbox/dcim/forms/bulk_edit.py:995 netbox/dcim/forms/bulk_import.py:1554 #: netbox/dcim/forms/bulk_edit.py:995 netbox/dcim/forms/bulk_import.py:1587
#: netbox/dcim/forms/filtersets.py:1253 netbox/templates/dcim/powerfeed.html:95 #: netbox/dcim/forms/filtersets.py:1253 netbox/templates/dcim/powerfeed.html:95
msgid "Phase" msgid "Phase"
msgstr "" msgstr ""
@@ -4652,7 +4653,7 @@ msgid "available options"
msgstr "" msgstr ""
#: netbox/dcim/forms/bulk_import.py:137 netbox/dcim/forms/bulk_import.py:601 #: netbox/dcim/forms/bulk_import.py:137 netbox/dcim/forms/bulk_import.py:601
#: netbox/dcim/forms/bulk_import.py:1510 netbox/ipam/forms/bulk_import.py:479 #: netbox/dcim/forms/bulk_import.py:1543 netbox/ipam/forms/bulk_import.py:479
#: netbox/virtualization/forms/bulk_import.py:64 #: netbox/virtualization/forms/bulk_import.py:64
#: netbox/virtualization/forms/bulk_import.py:95 #: netbox/virtualization/forms/bulk_import.py:95
msgid "Assigned site" msgid "Assigned site"
@@ -4715,7 +4716,7 @@ msgstr ""
msgid "Parent site" msgid "Parent site"
msgstr "" msgstr ""
#: netbox/dcim/forms/bulk_import.py:347 netbox/dcim/forms/bulk_import.py:1523 #: netbox/dcim/forms/bulk_import.py:347 netbox/dcim/forms/bulk_import.py:1556
msgid "Rack's location (if any)" msgid "Rack's location (if any)"
msgstr "" msgstr ""
@@ -4766,7 +4767,7 @@ msgstr ""
msgid "Limit platform assignments to this manufacturer" msgid "Limit platform assignments to this manufacturer"
msgstr "" msgstr ""
#: netbox/dcim/forms/bulk_import.py:534 netbox/dcim/forms/bulk_import.py:1593 #: netbox/dcim/forms/bulk_import.py:534 netbox/dcim/forms/bulk_import.py:1626
#: netbox/tenancy/forms/bulk_import.py:105 #: netbox/tenancy/forms/bulk_import.py:105
msgid "Assigned role" msgid "Assigned role"
msgstr "" msgstr ""
@@ -4956,7 +4957,7 @@ msgid "Corresponding rear port"
msgstr "" msgstr ""
#: netbox/dcim/forms/bulk_import.py:1020 netbox/dcim/forms/bulk_import.py:1061 #: netbox/dcim/forms/bulk_import.py:1020 netbox/dcim/forms/bulk_import.py:1061
#: netbox/dcim/forms/bulk_import.py:1384 #: netbox/dcim/forms/bulk_import.py:1398
msgid "Physical medium classification" msgid "Physical medium classification"
msgstr "" msgstr ""
@@ -5045,102 +5046,120 @@ msgid "Must specify the parent device or VM when assigning an interface"
msgstr "" msgstr ""
#: netbox/dcim/forms/bulk_import.py:1339 #: netbox/dcim/forms/bulk_import.py:1339
msgid "Side A site"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1343
#: netbox/wireless/forms/bulk_import.py:94
msgid "Site of parent device A (if any)"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1346
msgid "Side A device" msgid "Side A device"
msgstr "" msgstr ""
#: netbox/dcim/forms/bulk_import.py:1342 netbox/dcim/forms/bulk_import.py:1360 #: netbox/dcim/forms/bulk_import.py:1349 netbox/dcim/forms/bulk_import.py:1374
msgid "Device name" msgid "Device name"
msgstr "" msgstr ""
#: netbox/dcim/forms/bulk_import.py:1345 #: netbox/dcim/forms/bulk_import.py:1352
msgid "Side A type" msgid "Side A type"
msgstr "" msgstr ""
#: netbox/dcim/forms/bulk_import.py:1351 #: netbox/dcim/forms/bulk_import.py:1358
msgid "Side A name" msgid "Side A name"
msgstr "" msgstr ""
#: netbox/dcim/forms/bulk_import.py:1352 netbox/dcim/forms/bulk_import.py:1370 #: netbox/dcim/forms/bulk_import.py:1359 netbox/dcim/forms/bulk_import.py:1384
msgid "Termination name" msgid "Termination name"
msgstr "" msgstr ""
#: netbox/dcim/forms/bulk_import.py:1357 #: netbox/dcim/forms/bulk_import.py:1364
msgid "Side B site"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1368
#: netbox/wireless/forms/bulk_import.py:115
msgid "Site of parent device B (if any)"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1371
msgid "Side B device" msgid "Side B device"
msgstr "" msgstr ""
#: netbox/dcim/forms/bulk_import.py:1363 #: netbox/dcim/forms/bulk_import.py:1377
msgid "Side B type" msgid "Side B type"
msgstr "" msgstr ""
#: netbox/dcim/forms/bulk_import.py:1369 #: netbox/dcim/forms/bulk_import.py:1383
msgid "Side B name" msgid "Side B name"
msgstr "" msgstr ""
#: netbox/dcim/forms/bulk_import.py:1378 #: netbox/dcim/forms/bulk_import.py:1392
#: netbox/wireless/forms/bulk_import.py:134 #: netbox/wireless/forms/bulk_import.py:134
msgid "Connection status" msgid "Connection status"
msgstr "" msgstr ""
#: netbox/dcim/forms/bulk_import.py:1430 #: netbox/dcim/forms/bulk_import.py:1463
#, python-brace-format #, python-brace-format
msgid "Side {side_upper}: {device} {termination_object} is already connected" msgid "Side {side_upper}: {device} {termination_object} is already connected"
msgstr "" msgstr ""
#: netbox/dcim/forms/bulk_import.py:1436 #: netbox/dcim/forms/bulk_import.py:1469
#, python-brace-format #, python-brace-format
msgid "{side_upper} side termination not found: {device} {name}" msgid "{side_upper} side termination not found: {device} {name}"
msgstr "" msgstr ""
#: netbox/dcim/forms/bulk_import.py:1461 netbox/dcim/forms/model_forms.py:891 #: netbox/dcim/forms/bulk_import.py:1494 netbox/dcim/forms/model_forms.py:891
#: netbox/dcim/tables/devices.py:1069 netbox/templates/dcim/device.html:138 #: netbox/dcim/tables/devices.py:1069 netbox/templates/dcim/device.html:138
#: netbox/templates/dcim/virtualchassis.html:27 #: netbox/templates/dcim/virtualchassis.html:27
#: netbox/templates/dcim/virtualchassis.html:67 #: netbox/templates/dcim/virtualchassis.html:67
msgid "Master" msgid "Master"
msgstr "" msgstr ""
#: netbox/dcim/forms/bulk_import.py:1465 #: netbox/dcim/forms/bulk_import.py:1498
msgid "Master device" msgid "Master device"
msgstr "" msgstr ""
#: netbox/dcim/forms/bulk_import.py:1482 #: netbox/dcim/forms/bulk_import.py:1515
msgid "Name of parent site" msgid "Name of parent site"
msgstr "" msgstr ""
#: netbox/dcim/forms/bulk_import.py:1516 #: netbox/dcim/forms/bulk_import.py:1549
msgid "Upstream power panel" msgid "Upstream power panel"
msgstr "" msgstr ""
#: netbox/dcim/forms/bulk_import.py:1546 #: netbox/dcim/forms/bulk_import.py:1579
msgid "Primary or redundant" msgid "Primary or redundant"
msgstr "" msgstr ""
#: netbox/dcim/forms/bulk_import.py:1551 #: netbox/dcim/forms/bulk_import.py:1584
msgid "Supply type (AC/DC)" msgid "Supply type (AC/DC)"
msgstr "" msgstr ""
#: netbox/dcim/forms/bulk_import.py:1556 #: netbox/dcim/forms/bulk_import.py:1589
msgid "Single or three-phase" msgid "Single or three-phase"
msgstr "" msgstr ""
#: netbox/dcim/forms/bulk_import.py:1607 netbox/dcim/forms/model_forms.py:1847 #: netbox/dcim/forms/bulk_import.py:1640 netbox/dcim/forms/model_forms.py:1847
#: netbox/templates/dcim/device.html:196 #: netbox/templates/dcim/device.html:196
#: netbox/templates/dcim/virtualdevicecontext.html:30 #: netbox/templates/dcim/virtualdevicecontext.html:30
#: netbox/templates/virtualization/virtualmachine.html:52 #: netbox/templates/virtualization/virtualmachine.html:52
msgid "Primary IPv4" msgid "Primary IPv4"
msgstr "" msgstr ""
#: netbox/dcim/forms/bulk_import.py:1611 #: netbox/dcim/forms/bulk_import.py:1644
msgid "IPv4 address with mask, e.g. 1.2.3.4/24" msgid "IPv4 address with mask, e.g. 1.2.3.4/24"
msgstr "" msgstr ""
#: netbox/dcim/forms/bulk_import.py:1614 netbox/dcim/forms/model_forms.py:1856 #: netbox/dcim/forms/bulk_import.py:1647 netbox/dcim/forms/model_forms.py:1856
#: netbox/templates/dcim/device.html:212 #: netbox/templates/dcim/device.html:212
#: netbox/templates/dcim/virtualdevicecontext.html:41 #: netbox/templates/dcim/virtualdevicecontext.html:41
#: netbox/templates/virtualization/virtualmachine.html:68 #: netbox/templates/virtualization/virtualmachine.html:68
msgid "Primary IPv6" msgid "Primary IPv6"
msgstr "" msgstr ""
#: netbox/dcim/forms/bulk_import.py:1618 #: netbox/dcim/forms/bulk_import.py:1651
msgid "IPv6 address with prefix length, e.g. 2001:db8::1/64" msgid "IPv6 address with prefix length, e.g. 2001:db8::1/64"
msgstr "" msgstr ""
@@ -7784,7 +7803,7 @@ msgid "No"
msgstr "" msgstr ""
#: netbox/extras/choices.py:108 netbox/templates/tenancy/contact.html:67 #: netbox/extras/choices.py:108 netbox/templates/tenancy/contact.html:67
#: netbox/tenancy/forms/bulk_edit.py:125 #: netbox/tenancy/forms/bulk_edit.py:130
#: netbox/wireless/forms/model_forms.py:173 #: netbox/wireless/forms/model_forms.py:173
msgid "Link" msgid "Link"
msgstr "" msgstr ""
@@ -8325,7 +8344,7 @@ msgstr ""
#: netbox/netbox/navigation/menu.py:433 #: netbox/netbox/navigation/menu.py:433
#: netbox/templates/extras/notificationgroup.html:31 #: netbox/templates/extras/notificationgroup.html:31
#: netbox/templates/tenancy/contact.html:21 #: netbox/templates/tenancy/contact.html:21
#: netbox/tenancy/forms/bulk_edit.py:139 netbox/tenancy/forms/filtersets.py:78 #: netbox/tenancy/forms/bulk_edit.py:144 netbox/tenancy/forms/filtersets.py:78
#: netbox/tenancy/forms/model_forms.py:99 netbox/tenancy/tables/contacts.py:68 #: netbox/tenancy/forms/model_forms.py:99 netbox/tenancy/tables/contacts.py:68
#: netbox/users/forms/model_forms.py:182 netbox/users/forms/model_forms.py:194 #: netbox/users/forms/model_forms.py:182 netbox/users/forms/model_forms.py:194
#: netbox/users/forms/model_forms.py:306 netbox/users/tables.py:35 #: netbox/users/forms/model_forms.py:306 netbox/users/tables.py:35
@@ -9855,7 +9874,7 @@ msgstr ""
#: netbox/ipam/filtersets.py:466 netbox/ipam/filtersets.py:470 #: netbox/ipam/filtersets.py:466 netbox/ipam/filtersets.py:470
#: netbox/ipam/filtersets.py:562 netbox/ipam/forms/model_forms.py:506 #: netbox/ipam/filtersets.py:562 netbox/ipam/forms/model_forms.py:506
#: netbox/templates/tenancy/contact.html:63 #: netbox/templates/tenancy/contact.html:63
#: netbox/tenancy/forms/bulk_edit.py:120 #: netbox/tenancy/forms/bulk_edit.py:125
msgid "Address" msgid "Address"
msgstr "" msgstr ""
@@ -12311,7 +12330,7 @@ msgstr ""
#: netbox/templates/account/profile.html:27 #: netbox/templates/account/profile.html:27
#: netbox/templates/tenancy/contact.html:53 netbox/templates/users/user.html:23 #: netbox/templates/tenancy/contact.html:53 netbox/templates/users/user.html:23
#: netbox/tenancy/forms/bulk_edit.py:116 #: netbox/tenancy/forms/bulk_edit.py:121
msgid "Email" msgid "Email"
msgstr "" msgstr ""
@@ -14826,7 +14845,7 @@ msgid ""
msgstr "" msgstr ""
#: netbox/templates/tenancy/contact.html:18 netbox/tenancy/filtersets.py:152 #: netbox/templates/tenancy/contact.html:18 netbox/tenancy/filtersets.py:152
#: netbox/tenancy/forms/bulk_edit.py:149 netbox/tenancy/forms/filtersets.py:102 #: netbox/tenancy/forms/bulk_edit.py:154 netbox/tenancy/forms/filtersets.py:102
#: netbox/tenancy/forms/forms.py:57 netbox/tenancy/forms/model_forms.py:108 #: netbox/tenancy/forms/forms.py:57 netbox/tenancy/forms/model_forms.py:108
#: netbox/tenancy/forms/model_forms.py:132 #: netbox/tenancy/forms/model_forms.py:132
#: netbox/tenancy/tables/contacts.py:106 #: netbox/tenancy/tables/contacts.py:106
@@ -14834,12 +14853,12 @@ msgid "Contact"
msgstr "" msgstr ""
#: netbox/templates/tenancy/contact.html:39 #: netbox/templates/tenancy/contact.html:39
#: netbox/tenancy/forms/bulk_edit.py:106 #: netbox/tenancy/forms/bulk_edit.py:111
msgid "Title" msgid "Title"
msgstr "" msgstr ""
#: netbox/templates/tenancy/contact.html:43 #: netbox/templates/tenancy/contact.html:43
#: netbox/tenancy/forms/bulk_edit.py:111 netbox/tenancy/tables/contacts.py:72 #: netbox/tenancy/forms/bulk_edit.py:116 netbox/tenancy/tables/contacts.py:72
msgid "Phone" msgid "Phone"
msgstr "" msgstr ""
@@ -15208,15 +15227,15 @@ msgstr ""
msgid "Tenant Group (slug)" msgid "Tenant Group (slug)"
msgstr "" msgstr ""
#: netbox/tenancy/forms/bulk_edit.py:67 #: netbox/tenancy/forms/bulk_edit.py:72
msgid "Desciption" msgid "Desciption"
msgstr "" msgstr ""
#: netbox/tenancy/forms/bulk_edit.py:96 #: netbox/tenancy/forms/bulk_edit.py:101
msgid "Add groups" msgid "Add groups"
msgstr "" msgstr ""
#: netbox/tenancy/forms/bulk_edit.py:101 #: netbox/tenancy/forms/bulk_edit.py:106
msgid "Remove groups" msgid "Remove groups"
msgstr "" msgstr ""
@@ -16673,10 +16692,6 @@ msgstr ""
msgid "Bridged VLAN" msgid "Bridged VLAN"
msgstr "" msgstr ""
#: netbox/wireless/forms/bulk_import.py:94
msgid "Site of parent device A (if any)"
msgstr ""
#: netbox/wireless/forms/bulk_import.py:100 #: netbox/wireless/forms/bulk_import.py:100
msgid "Parent device of assigned interface A" msgid "Parent device of assigned interface A"
msgstr "" msgstr ""
@@ -16690,10 +16705,6 @@ msgstr ""
msgid "Assigned interface A" msgid "Assigned interface A"
msgstr "" msgstr ""
#: netbox/wireless/forms/bulk_import.py:115
msgid "Site of parent device B (if any)"
msgstr ""
#: netbox/wireless/forms/bulk_import.py:121 #: netbox/wireless/forms/bulk_import.py:121
msgid "Parent device of assigned interface B" msgid "Parent device of assigned interface B"
msgstr "" msgstr ""

View File

@@ -13,6 +13,8 @@ FILTER_CHAR_BASED_LOOKUP_MAP = dict(
ie='iexact', ie='iexact',
nie='iexact', nie='iexact',
empty='empty', empty='empty',
regex='regex',
iregex='iregex',
) )
FILTER_NUMERIC_BASED_LOOKUP_MAP = dict( FILTER_NUMERIC_BASED_LOOKUP_MAP = dict(

View File

@@ -180,6 +180,10 @@ class BaseFilterSetTest(TestCase):
self.assertEqual(self.filters['charfield__niew'].exclude, True) self.assertEqual(self.filters['charfield__niew'].exclude, True)
self.assertEqual(self.filters['charfield__empty'].lookup_expr, 'empty') self.assertEqual(self.filters['charfield__empty'].lookup_expr, 'empty')
self.assertEqual(self.filters['charfield__empty'].exclude, False) self.assertEqual(self.filters['charfield__empty'].exclude, False)
self.assertEqual(self.filters['charfield__regex'].lookup_expr, 'regex')
self.assertEqual(self.filters['charfield__regex'].exclude, False)
self.assertEqual(self.filters['charfield__iregex'].lookup_expr, 'iregex')
self.assertEqual(self.filters['charfield__iregex'].exclude, False)
def test_number_filter(self): def test_number_filter(self):
self.assertIsInstance(self.filters['numberfield'], django_filters.NumberFilter) self.assertIsInstance(self.filters['numberfield'], django_filters.NumberFilter)
@@ -220,6 +224,10 @@ class BaseFilterSetTest(TestCase):
self.assertEqual(self.filters['macaddressfield__iew'].exclude, False) self.assertEqual(self.filters['macaddressfield__iew'].exclude, False)
self.assertEqual(self.filters['macaddressfield__niew'].lookup_expr, 'iendswith') self.assertEqual(self.filters['macaddressfield__niew'].lookup_expr, 'iendswith')
self.assertEqual(self.filters['macaddressfield__niew'].exclude, True) self.assertEqual(self.filters['macaddressfield__niew'].exclude, True)
self.assertEqual(self.filters['macaddressfield__regex'].lookup_expr, 'regex')
self.assertEqual(self.filters['macaddressfield__regex'].exclude, False)
self.assertEqual(self.filters['macaddressfield__iregex'].lookup_expr, 'iregex')
self.assertEqual(self.filters['macaddressfield__iregex'].exclude, False)
def test_model_choice_filter(self): def test_model_choice_filter(self):
self.assertIsInstance(self.filters['modelchoicefield'], django_filters.ModelChoiceFilter) self.assertIsInstance(self.filters['modelchoicefield'], django_filters.ModelChoiceFilter)
@@ -257,6 +265,10 @@ class BaseFilterSetTest(TestCase):
self.assertEqual(self.filters['multivaluecharfield__iew'].exclude, False) self.assertEqual(self.filters['multivaluecharfield__iew'].exclude, False)
self.assertEqual(self.filters['multivaluecharfield__niew'].lookup_expr, 'iendswith') self.assertEqual(self.filters['multivaluecharfield__niew'].lookup_expr, 'iendswith')
self.assertEqual(self.filters['multivaluecharfield__niew'].exclude, True) self.assertEqual(self.filters['multivaluecharfield__niew'].exclude, True)
self.assertEqual(self.filters['multivaluecharfield__regex'].lookup_expr, 'regex')
self.assertEqual(self.filters['multivaluecharfield__regex'].exclude, False)
self.assertEqual(self.filters['multivaluecharfield__iregex'].lookup_expr, 'iregex')
self.assertEqual(self.filters['multivaluecharfield__iregex'].exclude, False)
def test_multi_value_date_filter(self): def test_multi_value_date_filter(self):
self.assertIsInstance(self.filters['datefield'], MultiValueDateFilter) self.assertIsInstance(self.filters['datefield'], MultiValueDateFilter)
@@ -340,6 +352,10 @@ class BaseFilterSetTest(TestCase):
self.assertEqual(self.filters['multiplechoicefield__iew'].exclude, False) self.assertEqual(self.filters['multiplechoicefield__iew'].exclude, False)
self.assertEqual(self.filters['multiplechoicefield__niew'].lookup_expr, 'iendswith') self.assertEqual(self.filters['multiplechoicefield__niew'].lookup_expr, 'iendswith')
self.assertEqual(self.filters['multiplechoicefield__niew'].exclude, True) self.assertEqual(self.filters['multiplechoicefield__niew'].exclude, True)
self.assertEqual(self.filters['multiplechoicefield__regex'].lookup_expr, 'regex')
self.assertEqual(self.filters['multiplechoicefield__regex'].exclude, False)
self.assertEqual(self.filters['multiplechoicefield__iregex'].lookup_expr, 'iregex')
self.assertEqual(self.filters['multiplechoicefield__iregex'].exclude, False)
def test_tag_filter(self): def test_tag_filter(self):
self.assertIsInstance(self.filters['tagfield'], TagFilter) self.assertIsInstance(self.filters['tagfield'], TagFilter)
@@ -534,6 +550,14 @@ class DynamicFilterLookupExpressionTest(TestCase):
params = {'slug__niew': ['-1']} params = {'slug__niew': ['-1']}
self.assertEqual(SiteFilterSet(params, Site.objects.all()).qs.count(), 2) self.assertEqual(SiteFilterSet(params, Site.objects.all()).qs.count(), 2)
def test_site_slug_regex(self):
params = {'slug__regex': ['^def-[a-z]*-2$']}
self.assertEqual(SiteFilterSet(params, Site.objects.all()).qs.count(), 1)
def test_site_slug_iregex(self):
params = {'slug__iregex': ['^DEF-[a-z]*-2$']}
self.assertEqual(SiteFilterSet(params, Site.objects.all()).qs.count(), 1)
def test_provider_asn_lt(self): def test_provider_asn_lt(self):
params = {'asn__lt': [65101]} params = {'asn__lt': [65101]}
self.assertEqual(ASNFilterSet(params, ASN.objects.all()).qs.count(), 1) self.assertEqual(ASNFilterSet(params, ASN.objects.all()).qs.count(), 1)
@@ -618,6 +642,14 @@ class DynamicFilterLookupExpressionTest(TestCase):
params = {'mac_address__nic': ['aa:', 'bb']} params = {'mac_address__nic': ['aa:', 'bb']}
self.assertEqual(DeviceFilterSet(params, Device.objects.all()).qs.count(), 1) self.assertEqual(DeviceFilterSet(params, Device.objects.all()).qs.count(), 1)
def test_device_mac_address_regex(self):
params = {'mac_address__regex': ['^cc.*:03$']}
self.assertEqual(DeviceFilterSet(params, Device.objects.all()).qs.count(), 1)
def test_device_mac_address_iregex(self):
params = {'mac_address__iregex': ['^CC.*:03$']}
self.assertEqual(DeviceFilterSet(params, Device.objects.all()).qs.count(), 1)
def test_interface_rf_role_empty(self): def test_interface_rf_role_empty(self):
params = {'rf_role__empty': 'true'} params = {'rf_role__empty': 'true'}
self.assertEqual(InterfaceFilterSet(params, Interface.objects.all()).qs.count(), 5) self.assertEqual(InterfaceFilterSet(params, Interface.objects.all()).qs.count(), 5)