mirror of
https://github.com/netbox-community/netbox.git
synced 2026-01-18 09:42:18 -06:00
Compare commits
11 Commits
v4.3.4
...
b01c75cf3a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b01c75cf3a | ||
|
|
ffa9a52667 | ||
|
|
47320f9958 | ||
|
|
d08a1bd07d | ||
|
|
14c4aeca54 | ||
|
|
26bec1275f | ||
|
|
fa2d7f6516 | ||
|
|
d571cb4867 | ||
|
|
2129355c30 | ||
|
|
c40bfb1445 | ||
|
|
b88b5b0b1b |
@@ -302,13 +302,6 @@ Quit the server with CONTROL-C.
|
|||||||
|
|
||||||
Next, connect to the name or IP of the server (as defined in `ALLOWED_HOSTS`) on port 8000; for example, <http://127.0.0.1:8000/>. You should be greeted with the NetBox home page. Try logging in using the username and password specified when creating a superuser.
|
Next, connect to the name or IP of the server (as defined in `ALLOWED_HOSTS`) on port 8000; for example, <http://127.0.0.1:8000/>. You should be greeted with the NetBox home page. Try logging in using the username and password specified when creating a superuser.
|
||||||
|
|
||||||
!!! note
|
|
||||||
By default RHEL based distros will likely block your testing attempts with firewalld. The development server port can be opened with `firewall-cmd` (add `--permanent` if you want the rule to survive server restarts):
|
|
||||||
|
|
||||||
```no-highlight
|
|
||||||
firewall-cmd --zone=public --add-port=8000/tcp
|
|
||||||
```
|
|
||||||
|
|
||||||
!!! danger "Not for production use"
|
!!! danger "Not for production use"
|
||||||
The development server is for development and testing purposes only. It is neither performant nor secure enough for production use. **Do not use it in production.**
|
The development server is for development and testing purposes only. It is neither performant nor secure enough for production use. **Do not use it in production.**
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|
||||||
|
|||||||
@@ -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'),
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import svgwrite
|
|||||||
from svgwrite.container import Hyperlink
|
from svgwrite.container import Hyperlink
|
||||||
from svgwrite.image import Image
|
from svgwrite.image import Image
|
||||||
from svgwrite.gradients import LinearGradient
|
from svgwrite.gradients import LinearGradient
|
||||||
|
from svgwrite.masking import ClipPath
|
||||||
from svgwrite.shapes import Rect
|
from svgwrite.shapes import Rect
|
||||||
from svgwrite.text import Text
|
from svgwrite.text import Text
|
||||||
|
|
||||||
@@ -67,6 +68,20 @@ def get_device_description(device):
|
|||||||
return description
|
return description
|
||||||
|
|
||||||
|
|
||||||
|
def truncate_text(text, width, font_size=15):
|
||||||
|
"""
|
||||||
|
Truncate text to fit within the width of a rectangle.
|
||||||
|
|
||||||
|
:param text: The text to truncate
|
||||||
|
:param width: Width of rectangle
|
||||||
|
:param font_size: Font size (default is 15, ~0.875rem)
|
||||||
|
"""
|
||||||
|
char_width = font_size * 0.6 # 0.6 is an approximation of the average character width in pixels
|
||||||
|
max_char = int(width / char_width)
|
||||||
|
|
||||||
|
return text if len(text) <= max_char else text[:max_char] + '...'
|
||||||
|
|
||||||
|
|
||||||
class RackElevationSVG:
|
class RackElevationSVG:
|
||||||
"""
|
"""
|
||||||
Use this class to render a rack elevation as an SVG image.
|
Use this class to render a rack elevation as an SVG image.
|
||||||
@@ -177,12 +192,26 @@ class RackElevationSVG:
|
|||||||
link = Hyperlink(href=f'{self.base_url}{device.get_absolute_url()}', target="_parent")
|
link = Hyperlink(href=f'{self.base_url}{device.get_absolute_url()}', target="_parent")
|
||||||
link.set_desc(description)
|
link.set_desc(description)
|
||||||
|
|
||||||
|
# Create clipPath element
|
||||||
|
# This is necessary as fallback because the truncate_text method is an approximation
|
||||||
|
clip_id = f"clip-{device.id}"
|
||||||
|
clip_path = ClipPath(id=clip_id)
|
||||||
|
clip_path.add(Rect(coords, size))
|
||||||
|
|
||||||
|
self.drawing.defs.add(clip_path)
|
||||||
|
|
||||||
|
# Name to display
|
||||||
|
display_name = truncate_text(name, size[0])
|
||||||
|
|
||||||
# Add rect element to hyperlink
|
# Add rect element to hyperlink
|
||||||
if color:
|
if color:
|
||||||
link.add(Rect(coords, size, style=f'fill: #{color}', class_=f'slot{css_extra}'))
|
link.add(Rect(coords, size, style=f'fill: #{color}', class_=f'slot{css_extra}'))
|
||||||
else:
|
else:
|
||||||
link.add(Rect(coords, size, class_=f'slot blocked{css_extra}'))
|
link.add(Rect(coords, size, class_=f'slot blocked{css_extra}'))
|
||||||
link.add(Text(name, insert=text_coords, fill=text_color, class_=f'label{css_extra}'))
|
link.add(
|
||||||
|
Text(display_name, insert=text_coords, fill=text_color, clip_path=f"url(#{clip_id})",
|
||||||
|
class_=f'label{css_extra}')
|
||||||
|
)
|
||||||
|
|
||||||
# Embed device type image if provided
|
# Embed device type image if provided
|
||||||
if self.include_images and image:
|
if self.include_images and image:
|
||||||
|
|||||||
@@ -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 = (
|
||||||
|
|||||||
2
netbox/project-static/dist/netbox.js
vendored
2
netbox/project-static/dist/netbox.js
vendored
File diff suppressed because one or more lines are too long
6
netbox/project-static/dist/netbox.js.map
vendored
6
netbox/project-static/dist/netbox.js.map
vendored
File diff suppressed because one or more lines are too long
@@ -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');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -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',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -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(
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user