Compare commits

..

9 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
Artem Kotik
c40bfb1445 Add regex and iregex filter lookup expressions and corresponding tests 2025-07-18 16:56:54 +02:00
7 changed files with 206 additions and 85 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:
| Filter | Description |
|---------|----------------------------------------|
| `n` | Not equal to |
| `ic` | Contains (case-insensitive) |
| `nic` | Does not contain (case-insensitive) |
| `isw` | Starts with (case-insensitive) |
| `nisw` | Does not start with (case-insensitive) |
| `iew` | Ends with (case-insensitive) |
| `niew` | Does not end with (case-insensitive) |
| `ie` | Exact match (case-insensitive) |
| `nie` | Inverse exact match (case-insensitive) |
| `empty` | Is empty/null (boolean) |
| Filter | Description |
|----------|----------------------------------------|
| `n` | Not equal to |
| `ic` | Contains (case-insensitive) |
| `nic` | Does not contain (case-insensitive) |
| `isw` | Starts with (case-insensitive) |
| `nisw` | Does not start with (case-insensitive) |
| `iew` | Ends with (case-insensitive) |
| `niew` | Does not end with (case-insensitive) |
| `ie` | Exact match (case-insensitive) |
| `nie` | Inverse exact match (case-insensitive) |
| `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:

View File

@@ -1410,6 +1410,12 @@ class CableImportForm(NetBoxModelImportForm):
required=False,
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:
model = Cable
@@ -1473,6 +1479,24 @@ class CableImportForm(NetBoxModelImportForm):
setattr(self.instance, f'{side}_terminations', [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):
return self._clean_side('a')
@@ -1484,11 +1508,14 @@ class CableImportForm(NetBoxModelImportForm):
length_unit = self.cleaned_data.get('length_unit', None)
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
#
class VirtualChassisImportForm(NetBoxModelImportForm):
master = CSVModelChoiceField(
label=_('Master'),

View File

@@ -1,5 +1,7 @@
from django.conf import settings
from django.http import Http404
from django.shortcuts import get_object_or_404
from django.views.static import serve
from django_rq.queues import get_connection
from drf_spectacular.utils import extend_schema, extend_schema_view
from rest_framework import status
@@ -200,6 +202,17 @@ class ImageAttachmentViewSet(NetBoxModelViewSet):
serializer_class = serializers.ImageAttachmentSerializer
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

View File

@@ -1,6 +1,8 @@
import datetime
from PIL import Image
from django.contrib.contenttypes.models import ContentType
from django.core.files.base import File
from django.urls import reverse
from django.utils.timezone import make_aware, now
from rest_framework import status
@@ -616,6 +618,38 @@ class ImageAttachmentTest(
)
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):
model = JournalEntry

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\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"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\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: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:598 netbox/dcim/forms/bulk_import.py:1479
#: netbox/dcim/forms/bulk_import.py:1507 netbox/dcim/forms/filtersets.py:89
#: netbox/dcim/forms/bulk_import.py:598 netbox/dcim/forms/bulk_import.py:1512
#: 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:441 netbox/dcim/forms/filtersets.py:773
#: 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/wirelesslangroup.html:33
#: 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:130 netbox/users/forms/bulk_edit.py:64
#: netbox/users/forms/bulk_edit.py:82 netbox/users/forms/bulk_edit.py:112
#: netbox/tenancy/forms/bulk_edit.py:32 netbox/tenancy/forms/bulk_edit.py:49
#: netbox/tenancy/forms/bulk_edit.py:87 netbox/tenancy/forms/bulk_edit.py:135
#: 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:47
#: 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: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:1060 netbox/dcim/forms/bulk_import.py:1381
#: netbox/dcim/forms/bulk_import.py:1544 netbox/dcim/forms/filtersets.py:1023
#: netbox/dcim/forms/bulk_import.py:1060 netbox/dcim/forms/bulk_import.py:1395
#: 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:1315 netbox/dcim/forms/filtersets.py:1340
#: 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_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:1168 netbox/dcim/forms/bulk_import.py:1375
#: netbox/dcim/forms/bulk_import.py:1539 netbox/dcim/forms/bulk_import.py:1603
#: netbox/dcim/forms/bulk_import.py:1168 netbox/dcim/forms/bulk_import.py:1389
#: 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:361 netbox/dcim/forms/filtersets.py:819
#: 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_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:537 netbox/dcim/forms/bulk_import.py:1387
#: netbox/dcim/forms/bulk_import.py:1596 netbox/dcim/forms/filtersets.py:175
#: netbox/dcim/forms/bulk_import.py:537 netbox/dcim/forms/bulk_import.py:1401
#: 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:401 netbox/dcim/forms/filtersets.py:422
#: 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/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"
msgstr ""
@@ -1065,7 +1066,7 @@ msgstr ""
#: netbox/templates/dcim/virtualchassis.html:68
#: netbox/templates/dcim/virtualchassis_edit.html:60
#: 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"
msgstr ""
@@ -1116,7 +1117,7 @@ msgstr ""
#: netbox/templates/virtualization/virtualmachine.html:23
#: netbox/templates/vpn/tunneltermination.html:17
#: 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/tables/contacts.py:110
#: 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/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: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:301 netbox/ipam/forms/bulk_import.py:498
#: netbox/ipam/forms/bulk_import.py:511
@@ -1164,8 +1165,8 @@ msgstr ""
#: 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: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:1600 netbox/ipam/forms/bulk_import.py:45
#: netbox/dcim/forms/bulk_import.py:1405 netbox/dcim/forms/bulk_import.py:1569
#: 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:122 netbox/ipam/forms/bulk_import.py:142
#: 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: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:604 netbox/dcim/forms/bulk_import.py:1485
#: netbox/dcim/forms/bulk_import.py:1519 netbox/dcim/forms/filtersets.py:97
#: netbox/dcim/forms/bulk_import.py:604 netbox/dcim/forms/bulk_import.py:1518
#: 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:398 netbox/dcim/forms/filtersets.py:449
#: 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: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: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:973 netbox/dcim/forms/filtersets.py:1119
#: 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/virtualization/vminterface.html:39
#: 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:58
#: 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_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:1530 netbox/dcim/forms/filtersets.py:106
#: netbox/dcim/forms/bulk_import.py:611 netbox/dcim/forms/bulk_import.py:1559
#: 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:421 netbox/dcim/forms/filtersets.py:459
#: netbox/dcim/forms/filtersets.py:792 netbox/dcim/forms/filtersets.py:1005
@@ -4394,8 +4395,8 @@ msgstr ""
msgid "Length"
msgstr ""
#: netbox/dcim/forms/bulk_edit.py:875 netbox/dcim/forms/bulk_import.py:1394
#: netbox/dcim/forms/bulk_import.py:1397 netbox/dcim/forms/filtersets.py:1140
#: netbox/dcim/forms/bulk_edit.py:875 netbox/dcim/forms/bulk_import.py:1408
#: netbox/dcim/forms/bulk_import.py:1411 netbox/dcim/forms/filtersets.py:1140
msgid "Length unit"
msgstr ""
@@ -4404,17 +4405,17 @@ msgstr ""
msgid "Domain"
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
msgid "Power panel"
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
msgid "Supply"
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
msgid "Phase"
msgstr ""
@@ -4652,7 +4653,7 @@ msgid "available options"
msgstr ""
#: 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:95
msgid "Assigned site"
@@ -4715,7 +4716,7 @@ msgstr ""
msgid "Parent site"
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)"
msgstr ""
@@ -4766,7 +4767,7 @@ msgstr ""
msgid "Limit platform assignments to this manufacturer"
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
msgid "Assigned role"
msgstr ""
@@ -4956,7 +4957,7 @@ msgid "Corresponding rear port"
msgstr ""
#: 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"
msgstr ""
@@ -5045,102 +5046,120 @@ msgid "Must specify the parent device or VM when assigning an interface"
msgstr ""
#: 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"
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"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1345
#: netbox/dcim/forms/bulk_import.py:1352
msgid "Side A type"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1351
#: netbox/dcim/forms/bulk_import.py:1358
msgid "Side A name"
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"
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"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1363
#: netbox/dcim/forms/bulk_import.py:1377
msgid "Side B type"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1369
#: netbox/dcim/forms/bulk_import.py:1383
msgid "Side B name"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1378
#: netbox/dcim/forms/bulk_import.py:1392
#: netbox/wireless/forms/bulk_import.py:134
msgid "Connection status"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1430
#: netbox/dcim/forms/bulk_import.py:1463
#, python-brace-format
msgid "Side {side_upper}: {device} {termination_object} is already connected"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1436
#: netbox/dcim/forms/bulk_import.py:1469
#, python-brace-format
msgid "{side_upper} side termination not found: {device} {name}"
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/templates/dcim/virtualchassis.html:27
#: netbox/templates/dcim/virtualchassis.html:67
msgid "Master"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1465
#: netbox/dcim/forms/bulk_import.py:1498
msgid "Master device"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1482
#: netbox/dcim/forms/bulk_import.py:1515
msgid "Name of parent site"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1516
#: netbox/dcim/forms/bulk_import.py:1549
msgid "Upstream power panel"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1546
#: netbox/dcim/forms/bulk_import.py:1579
msgid "Primary or redundant"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1551
#: netbox/dcim/forms/bulk_import.py:1584
msgid "Supply type (AC/DC)"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1556
#: netbox/dcim/forms/bulk_import.py:1589
msgid "Single or three-phase"
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/virtualdevicecontext.html:30
#: netbox/templates/virtualization/virtualmachine.html:52
msgid "Primary IPv4"
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"
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/virtualdevicecontext.html:41
#: netbox/templates/virtualization/virtualmachine.html:68
msgid "Primary IPv6"
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"
msgstr ""
@@ -7784,7 +7803,7 @@ msgid "No"
msgstr ""
#: 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
msgid "Link"
msgstr ""
@@ -8325,7 +8344,7 @@ msgstr ""
#: netbox/netbox/navigation/menu.py:433
#: netbox/templates/extras/notificationgroup.html:31
#: 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/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
@@ -9855,7 +9874,7 @@ msgstr ""
#: netbox/ipam/filtersets.py:466 netbox/ipam/filtersets.py:470
#: netbox/ipam/filtersets.py:562 netbox/ipam/forms/model_forms.py:506
#: netbox/templates/tenancy/contact.html:63
#: netbox/tenancy/forms/bulk_edit.py:120
#: netbox/tenancy/forms/bulk_edit.py:125
msgid "Address"
msgstr ""
@@ -12311,7 +12330,7 @@ msgstr ""
#: netbox/templates/account/profile.html:27
#: 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"
msgstr ""
@@ -14826,7 +14845,7 @@ msgid ""
msgstr ""
#: 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/model_forms.py:132
#: netbox/tenancy/tables/contacts.py:106
@@ -14834,12 +14853,12 @@ msgid "Contact"
msgstr ""
#: netbox/templates/tenancy/contact.html:39
#: netbox/tenancy/forms/bulk_edit.py:106
#: netbox/tenancy/forms/bulk_edit.py:111
msgid "Title"
msgstr ""
#: 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"
msgstr ""
@@ -15208,15 +15227,15 @@ msgstr ""
msgid "Tenant Group (slug)"
msgstr ""
#: netbox/tenancy/forms/bulk_edit.py:67
#: netbox/tenancy/forms/bulk_edit.py:72
msgid "Desciption"
msgstr ""
#: netbox/tenancy/forms/bulk_edit.py:96
#: netbox/tenancy/forms/bulk_edit.py:101
msgid "Add groups"
msgstr ""
#: netbox/tenancy/forms/bulk_edit.py:101
#: netbox/tenancy/forms/bulk_edit.py:106
msgid "Remove groups"
msgstr ""
@@ -16673,10 +16692,6 @@ msgstr ""
msgid "Bridged VLAN"
msgstr ""
#: netbox/wireless/forms/bulk_import.py:94
msgid "Site of parent device A (if any)"
msgstr ""
#: netbox/wireless/forms/bulk_import.py:100
msgid "Parent device of assigned interface A"
msgstr ""
@@ -16690,10 +16705,6 @@ msgstr ""
msgid "Assigned interface A"
msgstr ""
#: netbox/wireless/forms/bulk_import.py:115
msgid "Site of parent device B (if any)"
msgstr ""
#: netbox/wireless/forms/bulk_import.py:121
msgid "Parent device of assigned interface B"
msgstr ""

View File

@@ -13,6 +13,8 @@ FILTER_CHAR_BASED_LOOKUP_MAP = dict(
ie='iexact',
nie='iexact',
empty='empty',
regex='regex',
iregex='iregex',
)
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__empty'].lookup_expr, 'empty')
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):
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__niew'].lookup_expr, 'iendswith')
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):
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__niew'].lookup_expr, 'iendswith')
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):
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__niew'].lookup_expr, 'iendswith')
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):
self.assertIsInstance(self.filters['tagfield'], TagFilter)
@@ -534,6 +550,14 @@ class DynamicFilterLookupExpressionTest(TestCase):
params = {'slug__niew': ['-1']}
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):
params = {'asn__lt': [65101]}
self.assertEqual(ASNFilterSet(params, ASN.objects.all()).qs.count(), 1)
@@ -618,6 +642,14 @@ class DynamicFilterLookupExpressionTest(TestCase):
params = {'mac_address__nic': ['aa:', 'bb']}
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):
params = {'rf_role__empty': 'true'}
self.assertEqual(InterfaceFilterSet(params, Interface.objects.all()).qs.count(), 5)