mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-25 01:48:38 -06:00
Improve filtering cables by termination device/rack/site
This commit is contained in:
parent
11707cb3b1
commit
42e5282283
@ -1537,27 +1537,35 @@ class CableFilterSet(TenancyFilterSet, NetBoxModelFilterSet):
|
|||||||
choices=ColorChoices
|
choices=ColorChoices
|
||||||
)
|
)
|
||||||
device_id = MultiValueNumberFilter(
|
device_id = MultiValueNumberFilter(
|
||||||
method='filter_device'
|
method='filter_by_termination'
|
||||||
)
|
)
|
||||||
device = MultiValueCharFilter(
|
device = MultiValueCharFilter(
|
||||||
method='filter_device',
|
method='filter_by_termination',
|
||||||
field_name='device__name'
|
field_name='device__name'
|
||||||
)
|
)
|
||||||
rack_id = MultiValueNumberFilter(
|
rack_id = MultiValueNumberFilter(
|
||||||
method='filter_device',
|
method='filter_by_termination',
|
||||||
field_name='device__rack_id'
|
field_name='rack_id'
|
||||||
)
|
)
|
||||||
rack = MultiValueCharFilter(
|
rack = MultiValueCharFilter(
|
||||||
method='filter_device',
|
method='filter_by_termination',
|
||||||
field_name='device__rack__name'
|
field_name='rack__name'
|
||||||
|
)
|
||||||
|
location_id = MultiValueNumberFilter(
|
||||||
|
method='filter_by_termination',
|
||||||
|
field_name='location_id'
|
||||||
|
)
|
||||||
|
location = MultiValueCharFilter(
|
||||||
|
method='filter_by_termination',
|
||||||
|
field_name='location__name'
|
||||||
)
|
)
|
||||||
site_id = MultiValueNumberFilter(
|
site_id = MultiValueNumberFilter(
|
||||||
method='filter_device',
|
method='filter_by_termination',
|
||||||
field_name='device__site_id'
|
field_name='site_id'
|
||||||
)
|
)
|
||||||
site = MultiValueCharFilter(
|
site = MultiValueCharFilter(
|
||||||
method='filter_device',
|
method='filter_by_termination',
|
||||||
field_name='device__site__slug'
|
field_name='site__slug'
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -1569,12 +1577,10 @@ class CableFilterSet(TenancyFilterSet, NetBoxModelFilterSet):
|
|||||||
return queryset
|
return queryset
|
||||||
return queryset.filter(label__icontains=value)
|
return queryset.filter(label__icontains=value)
|
||||||
|
|
||||||
def filter_device(self, queryset, name, value):
|
def filter_by_termination(self, queryset, name, value):
|
||||||
queryset = queryset.filter(
|
# Filter by a related object cached on CableTermination. Note the underscore preceding the field name.
|
||||||
Q(**{f'_termination_a_{name}__in': value}) |
|
# Supported objects: device, rack, location, site
|
||||||
Q(**{f'_termination_b_{name}__in': value})
|
return queryset.filter(**{f'terminations___{name}__in': value}).distinct()
|
||||||
)
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
|
|
||||||
class CableTerminationFilterSet(BaseFilterSet):
|
class CableTerminationFilterSet(BaseFilterSet):
|
||||||
|
@ -730,7 +730,7 @@ class CableFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
|||||||
model = Cable
|
model = Cable
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'tag')),
|
(None, ('q', 'tag')),
|
||||||
('Location', ('site_id', 'rack_id', 'device_id')),
|
('Location', ('site_id', 'location_id', 'rack_id', 'device_id')),
|
||||||
('Attributes', ('type', 'status', 'color', 'length', 'length_unit')),
|
('Attributes', ('type', 'status', 'color', 'length', 'length_unit')),
|
||||||
('Tenant', ('tenant_group_id', 'tenant_id')),
|
('Tenant', ('tenant_group_id', 'tenant_id')),
|
||||||
)
|
)
|
||||||
@ -747,13 +747,23 @@ class CableFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
|||||||
},
|
},
|
||||||
label=_('Site')
|
label=_('Site')
|
||||||
)
|
)
|
||||||
|
location_id = DynamicModelMultipleChoiceField(
|
||||||
|
queryset=Location.objects.all(),
|
||||||
|
required=False,
|
||||||
|
label=_('Location'),
|
||||||
|
null_option='None',
|
||||||
|
query_params={
|
||||||
|
'site_id': '$site_id'
|
||||||
|
}
|
||||||
|
)
|
||||||
rack_id = DynamicModelMultipleChoiceField(
|
rack_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=Rack.objects.all(),
|
queryset=Rack.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Rack'),
|
label=_('Rack'),
|
||||||
null_option='None',
|
null_option='None',
|
||||||
query_params={
|
query_params={
|
||||||
'site_id': '$site_id'
|
'site_id': '$site_id',
|
||||||
|
'location_id': '$location_id',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
device_id = DynamicModelMultipleChoiceField(
|
device_id = DynamicModelMultipleChoiceField(
|
||||||
@ -761,8 +771,9 @@ class CableFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
|||||||
required=False,
|
required=False,
|
||||||
query_params={
|
query_params={
|
||||||
'site_id': '$site_id',
|
'site_id': '$site_id',
|
||||||
'tenant_id': '$tenant_id',
|
'location_id': '$location_id',
|
||||||
'rack_id': '$rack_id',
|
'rack_id': '$rack_id',
|
||||||
|
'tenant_id': '$tenant_id',
|
||||||
},
|
},
|
||||||
label=_('Device')
|
label=_('Device')
|
||||||
)
|
)
|
||||||
|
@ -20,6 +20,10 @@ class Migration(migrations.Migration):
|
|||||||
('termination_id', models.PositiveBigIntegerField()),
|
('termination_id', models.PositiveBigIntegerField()),
|
||||||
('cable', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='terminations', to='dcim.cable')),
|
('cable', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='terminations', to='dcim.cable')),
|
||||||
('termination_type', models.ForeignKey(limit_choices_to=models.Q(models.Q(models.Q(('app_label', 'circuits'), ('model__in', ('circuittermination',))), models.Q(('app_label', 'dcim'), ('model__in', ('consoleport', 'consoleserverport', 'frontport', 'interface', 'powerfeed', 'poweroutlet', 'powerport', 'rearport'))), _connector='OR')), on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.contenttype')),
|
('termination_type', models.ForeignKey(limit_choices_to=models.Q(models.Q(models.Q(('app_label', 'circuits'), ('model__in', ('circuittermination',))), models.Q(('app_label', 'dcim'), ('model__in', ('consoleport', 'consoleserverport', 'frontport', 'interface', 'powerfeed', 'poweroutlet', 'powerport', 'rearport'))), _connector='OR')), on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.contenttype')),
|
||||||
|
('_device', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='dcim.device')),
|
||||||
|
('_rack', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='dcim.rack')),
|
||||||
|
('_location', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='dcim.location')),
|
||||||
|
('_site', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='dcim.site')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'ordering': ('cable', 'cable_end', 'pk'),
|
'ordering': ('cable', 'cable_end', 'pk'),
|
||||||
|
@ -1,10 +1,37 @@
|
|||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
def cache_related_objects(termination):
|
||||||
|
"""
|
||||||
|
Replicate caching logic from CableTermination.cache_related_objects()
|
||||||
|
"""
|
||||||
|
attrs = {}
|
||||||
|
|
||||||
|
# Device components
|
||||||
|
if getattr(termination, 'device', None):
|
||||||
|
attrs['_device'] = termination.device
|
||||||
|
attrs['_rack'] = termination.device.rack
|
||||||
|
attrs['_location'] = termination.device.location
|
||||||
|
attrs['_site'] = termination.device.site
|
||||||
|
|
||||||
|
# Power feeds
|
||||||
|
elif getattr(termination, 'rack', None):
|
||||||
|
attrs['_rack'] = termination.rack
|
||||||
|
attrs['_location'] = termination.rack.location
|
||||||
|
attrs['_site'] = termination.rack.site
|
||||||
|
|
||||||
|
# Circuit terminations
|
||||||
|
elif getattr(termination, 'site', None):
|
||||||
|
attrs['_site'] = termination.site
|
||||||
|
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
def populate_cable_terminations(apps, schema_editor):
|
def populate_cable_terminations(apps, schema_editor):
|
||||||
"""
|
"""
|
||||||
Replicate terminations from the Cable model into CableTermination instances.
|
Replicate terminations from the Cable model into CableTermination instances.
|
||||||
"""
|
"""
|
||||||
|
ContentType = apps.get_model('contenttypes', 'ContentType')
|
||||||
Cable = apps.get_model('dcim', 'Cable')
|
Cable = apps.get_model('dcim', 'Cable')
|
||||||
CableTermination = apps.get_model('dcim', 'CableTermination')
|
CableTermination = apps.get_model('dcim', 'CableTermination')
|
||||||
|
|
||||||
@ -16,22 +43,20 @@ def populate_cable_terminations(apps, schema_editor):
|
|||||||
# Queue CableTerminations to be created
|
# Queue CableTerminations to be created
|
||||||
cable_terminations = []
|
cable_terminations = []
|
||||||
for i, cable in enumerate(cables, start=1):
|
for i, cable in enumerate(cables, start=1):
|
||||||
cable_terminations.append(
|
for cable_end in ('a', 'b'):
|
||||||
CableTermination(
|
# We must manually instantiate the termination object, because GFK fields are not
|
||||||
|
# supported within migrations.
|
||||||
|
termination_ct = ContentType.objects.get(pk=cable[f'termination_{cable_end}_type'])
|
||||||
|
termination_model = apps.get_model(termination_ct.app_label, termination_ct.model)
|
||||||
|
termination = termination_model.objects.get(pk=cable[f'termination_{cable_end}_id'])
|
||||||
|
|
||||||
|
cable_terminations.append(CableTermination(
|
||||||
cable_id=cable['id'],
|
cable_id=cable['id'],
|
||||||
cable_end='A',
|
cable_end=cable_end.upper(),
|
||||||
termination_type_id=cable['termination_a_type'],
|
termination_type_id=cable[f'termination_{cable_end}_type'],
|
||||||
termination_id=cable['termination_a_id']
|
termination_id=cable[f'termination_{cable_end}_id'],
|
||||||
)
|
**cache_related_objects(termination)
|
||||||
)
|
))
|
||||||
cable_terminations.append(
|
|
||||||
CableTermination(
|
|
||||||
cable_id=cable['id'],
|
|
||||||
cable_end='B',
|
|
||||||
termination_type_id=cable['termination_b_type'],
|
|
||||||
termination_id=cable['termination_b_id']
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Bulk create the termination objects
|
# Bulk create the termination objects
|
||||||
CableTermination.objects.bulk_create(cable_terminations, batch_size=100)
|
CableTermination.objects.bulk_create(cable_terminations, batch_size=100)
|
||||||
|
@ -34,6 +34,14 @@ class Migration(migrations.Migration):
|
|||||||
model_name='cable',
|
model_name='cable',
|
||||||
name='termination_b_type',
|
name='termination_b_type',
|
||||||
),
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='cable',
|
||||||
|
name='_termination_a_device',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='cable',
|
||||||
|
name='_termination_b_device',
|
||||||
|
),
|
||||||
|
|
||||||
# Remove old fields from CablePath
|
# Remove old fields from CablePath
|
||||||
migrations.AlterUniqueTogether(
|
migrations.AlterUniqueTogether(
|
||||||
|
@ -19,7 +19,6 @@ from utilities.querysets import RestrictedQuerySet
|
|||||||
from utilities.utils import to_meters
|
from utilities.utils import to_meters
|
||||||
from wireless.models import WirelessLink
|
from wireless.models import WirelessLink
|
||||||
from .device_components import FrontPort, RearPort
|
from .device_components import FrontPort, RearPort
|
||||||
from .devices import Device
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'Cable',
|
'Cable',
|
||||||
@ -81,22 +80,6 @@ class Cable(NetBoxModel):
|
|||||||
blank=True,
|
blank=True,
|
||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
# Cache the associated device (where applicable) for the A and B terminations. This enables filtering of Cables by
|
|
||||||
# their associated Devices.
|
|
||||||
_termination_a_device = models.ForeignKey(
|
|
||||||
to=Device,
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
related_name='+',
|
|
||||||
blank=True,
|
|
||||||
null=True
|
|
||||||
)
|
|
||||||
_termination_b_device = models.ForeignKey(
|
|
||||||
to=Device,
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
related_name='+',
|
|
||||||
blank=True,
|
|
||||||
null=True
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('pk',)
|
ordering = ('pk',)
|
||||||
@ -167,13 +150,6 @@ class Cable(NetBoxModel):
|
|||||||
else:
|
else:
|
||||||
self._abs_length = None
|
self._abs_length = None
|
||||||
|
|
||||||
# TODO: Need to come with a proper solution for filtering by termination parent
|
|
||||||
# Store the parent Device for the A and B terminations (if applicable) to enable filtering
|
|
||||||
if hasattr(self, 'a_terminations'):
|
|
||||||
self._termination_a_device = getattr(self.a_terminations[0], 'device', None)
|
|
||||||
if hasattr(self, 'b_terminations'):
|
|
||||||
self._termination_b_device = getattr(self.b_terminations[0], 'device', None)
|
|
||||||
|
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
# Update the private pk used in __str__ in case this is a new object (i.e. just got its pk)
|
# Update the private pk used in __str__ in case this is a new object (i.e. just got its pk)
|
||||||
@ -247,6 +223,32 @@ class CableTermination(models.Model):
|
|||||||
fk_field='termination_id'
|
fk_field='termination_id'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Cached associations to enable efficient filtering
|
||||||
|
_device = models.ForeignKey(
|
||||||
|
to='dcim.Device',
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
blank=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
_rack = models.ForeignKey(
|
||||||
|
to='dcim.Rack',
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
blank=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
_location = models.ForeignKey(
|
||||||
|
to='dcim.Location',
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
blank=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
_site = models.ForeignKey(
|
||||||
|
to='dcim.Site',
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
blank=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
|
||||||
objects = RestrictedQuerySet.as_manager()
|
objects = RestrictedQuerySet.as_manager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -277,6 +279,10 @@ class CableTermination(models.Model):
|
|||||||
})
|
})
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
|
|
||||||
|
# Cache objects associated with the terminating object (for filtering)
|
||||||
|
self.cache_related_objects()
|
||||||
|
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
# Set the cable on the terminating object
|
# Set the cable on the terminating object
|
||||||
@ -297,6 +303,30 @@ class CableTermination(models.Model):
|
|||||||
|
|
||||||
super().delete(*args, **kwargs)
|
super().delete(*args, **kwargs)
|
||||||
|
|
||||||
|
def cache_related_objects(self):
|
||||||
|
"""
|
||||||
|
Cache objects related to the termination (e.g. device, rack, site) directly on the object to
|
||||||
|
enable efficient filtering.
|
||||||
|
"""
|
||||||
|
assert self.termination is not None
|
||||||
|
|
||||||
|
# Device components
|
||||||
|
if getattr(self.termination, 'device', None):
|
||||||
|
self._device = self.termination.device
|
||||||
|
self._rack = self.termination.device.rack
|
||||||
|
self._location = self.termination.device.location
|
||||||
|
self._site = self.termination.device.site
|
||||||
|
|
||||||
|
# Power feeds
|
||||||
|
elif getattr(self.termination, 'rack', None):
|
||||||
|
self._rack = self.termination.rack
|
||||||
|
self._location = self.termination.rack.location
|
||||||
|
self._site = self.termination.rack.site
|
||||||
|
|
||||||
|
# Circuit terminations
|
||||||
|
elif getattr(self.termination, 'site', None):
|
||||||
|
self._site = self.termination.site
|
||||||
|
|
||||||
|
|
||||||
class CablePath(models.Model):
|
class CablePath(models.Model):
|
||||||
"""
|
"""
|
||||||
|
@ -126,6 +126,13 @@ class CabledObjectModel(models.Model):
|
|||||||
help_text="Treat as if a cable is connected"
|
help_text="Treat as if a cable is connected"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
cable_terminations = GenericRelation(
|
||||||
|
to='dcim.CableTermination',
|
||||||
|
content_type_field='termination_type',
|
||||||
|
object_id_field='termination_id',
|
||||||
|
related_query_name='%(class)s',
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
|
@ -3663,6 +3663,21 @@ class CableTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
)
|
)
|
||||||
Site.objects.bulk_create(sites)
|
Site.objects.bulk_create(sites)
|
||||||
|
|
||||||
|
locations = (
|
||||||
|
Location(name='Location 1', site=sites[0], slug='location-1'),
|
||||||
|
Location(name='Location 2', site=sites[1], slug='location-1'),
|
||||||
|
Location(name='Location 3', site=sites[2], slug='location-1'),
|
||||||
|
)
|
||||||
|
for location in locations:
|
||||||
|
location.save()
|
||||||
|
|
||||||
|
racks = (
|
||||||
|
Rack(name='Rack 1', site=sites[0], location=locations[0]),
|
||||||
|
Rack(name='Rack 2', site=sites[1], location=locations[1]),
|
||||||
|
Rack(name='Rack 3', site=sites[2], location=locations[2]),
|
||||||
|
)
|
||||||
|
Rack.objects.bulk_create(racks)
|
||||||
|
|
||||||
tenants = (
|
tenants = (
|
||||||
Tenant(name='Tenant 1', slug='tenant-1'),
|
Tenant(name='Tenant 1', slug='tenant-1'),
|
||||||
Tenant(name='Tenant 2', slug='tenant-2'),
|
Tenant(name='Tenant 2', slug='tenant-2'),
|
||||||
@ -3670,24 +3685,17 @@ class CableTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
)
|
)
|
||||||
Tenant.objects.bulk_create(tenants)
|
Tenant.objects.bulk_create(tenants)
|
||||||
|
|
||||||
racks = (
|
|
||||||
Rack(name='Rack 1', site=sites[0]),
|
|
||||||
Rack(name='Rack 2', site=sites[1]),
|
|
||||||
Rack(name='Rack 3', site=sites[2]),
|
|
||||||
)
|
|
||||||
Rack.objects.bulk_create(racks)
|
|
||||||
|
|
||||||
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
|
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
|
||||||
device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Model 1', slug='model-1')
|
device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Model 1', slug='model-1')
|
||||||
device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1')
|
device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1')
|
||||||
|
|
||||||
devices = (
|
devices = (
|
||||||
Device(name='Device 1', device_type=device_type, device_role=device_role, site=sites[0], rack=racks[0], position=1),
|
Device(name='Device 1', device_type=device_type, device_role=device_role, site=sites[0], rack=racks[0], location=locations[0], position=1),
|
||||||
Device(name='Device 2', device_type=device_type, device_role=device_role, site=sites[0], rack=racks[0], position=2),
|
Device(name='Device 2', device_type=device_type, device_role=device_role, site=sites[0], rack=racks[0], location=locations[0], position=2),
|
||||||
Device(name='Device 3', device_type=device_type, device_role=device_role, site=sites[1], rack=racks[1], position=1),
|
Device(name='Device 3', device_type=device_type, device_role=device_role, site=sites[1], rack=racks[1], location=locations[1], position=1),
|
||||||
Device(name='Device 4', device_type=device_type, device_role=device_role, site=sites[1], rack=racks[1], position=2),
|
Device(name='Device 4', device_type=device_type, device_role=device_role, site=sites[1], rack=racks[1], location=locations[1], position=2),
|
||||||
Device(name='Device 5', device_type=device_type, device_role=device_role, site=sites[2], rack=racks[2], position=1),
|
Device(name='Device 5', device_type=device_type, device_role=device_role, site=sites[2], rack=racks[2], location=locations[2], position=1),
|
||||||
Device(name='Device 6', device_type=device_type, device_role=device_role, site=sites[2], rack=racks[2], position=2),
|
Device(name='Device 6', device_type=device_type, device_role=device_role, site=sites[2], rack=racks[2], location=locations[2], position=2),
|
||||||
)
|
)
|
||||||
Device.objects.bulk_create(devices)
|
Device.objects.bulk_create(devices)
|
||||||
|
|
||||||
@ -3759,6 +3767,13 @@ class CableTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
params = {'rack': [racks[0].name, racks[1].name]}
|
params = {'rack': [racks[0].name, racks[1].name]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
||||||
|
|
||||||
|
def test_location(self):
|
||||||
|
locations = Location.objects.all()[:2]
|
||||||
|
params = {'location_id': [locations[0].pk, locations[1].pk]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
||||||
|
params = {'location': [locations[0].name, locations[1].name]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
||||||
|
|
||||||
def test_site(self):
|
def test_site(self):
|
||||||
site = Site.objects.all()[:2]
|
site = Site.objects.all()[:2]
|
||||||
params = {'site_id': [site[0].pk, site[1].pk]}
|
params = {'site_id': [site[0].pk, site[1].pk]}
|
||||||
|
Loading…
Reference in New Issue
Block a user