1import django_filters 2import netaddr 3from django.contrib.contenttypes.models import ContentType 4from django.core.exceptions import ValidationError 5from django.db.models import Q 6from netaddr.core import AddrFormatError 7 8from dcim.models import Device, Interface, Region, Site, SiteGroup 9from extras.filters import TagFilter 10from netbox.filtersets import OrganizationalModelFilterSet, PrimaryModelFilterSet 11from tenancy.filtersets import TenancyFilterSet 12from utilities.filters import ( 13 ContentTypeFilter, MultiValueCharFilter, MultiValueNumberFilter, NumericArrayFilter, TreeNodeMultipleChoiceFilter, 14) 15from virtualization.models import VirtualMachine, VMInterface 16from .choices import * 17from .models import * 18 19 20__all__ = ( 21 'AggregateFilterSet', 22 'IPAddressFilterSet', 23 'IPRangeFilterSet', 24 'PrefixFilterSet', 25 'RIRFilterSet', 26 'RoleFilterSet', 27 'RouteTargetFilterSet', 28 'ServiceFilterSet', 29 'VLANFilterSet', 30 'VLANGroupFilterSet', 31 'VRFFilterSet', 32) 33 34 35class VRFFilterSet(PrimaryModelFilterSet, TenancyFilterSet): 36 q = django_filters.CharFilter( 37 method='search', 38 label='Search', 39 ) 40 import_target_id = django_filters.ModelMultipleChoiceFilter( 41 field_name='import_targets', 42 queryset=RouteTarget.objects.all(), 43 label='Import target', 44 ) 45 import_target = django_filters.ModelMultipleChoiceFilter( 46 field_name='import_targets__name', 47 queryset=RouteTarget.objects.all(), 48 to_field_name='name', 49 label='Import target (name)', 50 ) 51 export_target_id = django_filters.ModelMultipleChoiceFilter( 52 field_name='export_targets', 53 queryset=RouteTarget.objects.all(), 54 label='Export target', 55 ) 56 export_target = django_filters.ModelMultipleChoiceFilter( 57 field_name='export_targets__name', 58 queryset=RouteTarget.objects.all(), 59 to_field_name='name', 60 label='Export target (name)', 61 ) 62 tag = TagFilter() 63 64 def search(self, queryset, name, value): 65 if not value.strip(): 66 return queryset 67 return queryset.filter( 68 Q(name__icontains=value) | 69 Q(rd__icontains=value) | 70 Q(description__icontains=value) 71 ) 72 73 class Meta: 74 model = VRF 75 fields = ['id', 'name', 'rd', 'enforce_unique'] 76 77 78class RouteTargetFilterSet(PrimaryModelFilterSet, TenancyFilterSet): 79 q = django_filters.CharFilter( 80 method='search', 81 label='Search', 82 ) 83 importing_vrf_id = django_filters.ModelMultipleChoiceFilter( 84 field_name='importing_vrfs', 85 queryset=VRF.objects.all(), 86 label='Importing VRF', 87 ) 88 importing_vrf = django_filters.ModelMultipleChoiceFilter( 89 field_name='importing_vrfs__rd', 90 queryset=VRF.objects.all(), 91 to_field_name='rd', 92 label='Import VRF (RD)', 93 ) 94 exporting_vrf_id = django_filters.ModelMultipleChoiceFilter( 95 field_name='exporting_vrfs', 96 queryset=VRF.objects.all(), 97 label='Exporting VRF', 98 ) 99 exporting_vrf = django_filters.ModelMultipleChoiceFilter( 100 field_name='exporting_vrfs__rd', 101 queryset=VRF.objects.all(), 102 to_field_name='rd', 103 label='Export VRF (RD)', 104 ) 105 tag = TagFilter() 106 107 def search(self, queryset, name, value): 108 if not value.strip(): 109 return queryset 110 return queryset.filter( 111 Q(name__icontains=value) | 112 Q(description__icontains=value) 113 ) 114 115 class Meta: 116 model = RouteTarget 117 fields = ['id', 'name'] 118 119 120class RIRFilterSet(OrganizationalModelFilterSet): 121 122 class Meta: 123 model = RIR 124 fields = ['id', 'name', 'slug', 'is_private', 'description'] 125 126 127class AggregateFilterSet(PrimaryModelFilterSet, TenancyFilterSet): 128 q = django_filters.CharFilter( 129 method='search', 130 label='Search', 131 ) 132 family = django_filters.NumberFilter( 133 field_name='prefix', 134 lookup_expr='family' 135 ) 136 prefix = django_filters.CharFilter( 137 method='filter_prefix', 138 label='Prefix', 139 ) 140 rir_id = django_filters.ModelMultipleChoiceFilter( 141 queryset=RIR.objects.all(), 142 label='RIR (ID)', 143 ) 144 rir = django_filters.ModelMultipleChoiceFilter( 145 field_name='rir__slug', 146 queryset=RIR.objects.all(), 147 to_field_name='slug', 148 label='RIR (slug)', 149 ) 150 tag = TagFilter() 151 152 class Meta: 153 model = Aggregate 154 fields = ['id', 'date_added'] 155 156 def search(self, queryset, name, value): 157 if not value.strip(): 158 return queryset 159 qs_filter = Q(description__icontains=value) 160 try: 161 prefix = str(netaddr.IPNetwork(value.strip()).cidr) 162 qs_filter |= Q(prefix__net_contains_or_equals=prefix) 163 except (AddrFormatError, ValueError): 164 pass 165 return queryset.filter(qs_filter) 166 167 def filter_prefix(self, queryset, name, value): 168 if not value.strip(): 169 return queryset 170 try: 171 query = str(netaddr.IPNetwork(value).cidr) 172 return queryset.filter(prefix=query) 173 except (AddrFormatError, ValueError): 174 return queryset.none() 175 176 177class RoleFilterSet(OrganizationalModelFilterSet): 178 q = django_filters.CharFilter( 179 method='search', 180 label='Search', 181 ) 182 183 class Meta: 184 model = Role 185 fields = ['id', 'name', 'slug'] 186 187 188class PrefixFilterSet(PrimaryModelFilterSet, TenancyFilterSet): 189 q = django_filters.CharFilter( 190 method='search', 191 label='Search', 192 ) 193 family = django_filters.NumberFilter( 194 field_name='prefix', 195 lookup_expr='family' 196 ) 197 prefix = MultiValueCharFilter( 198 method='filter_prefix', 199 label='Prefix', 200 ) 201 within = django_filters.CharFilter( 202 method='search_within', 203 label='Within prefix', 204 ) 205 within_include = django_filters.CharFilter( 206 method='search_within_include', 207 label='Within and including prefix', 208 ) 209 contains = django_filters.CharFilter( 210 method='search_contains', 211 label='Prefixes which contain this prefix or IP', 212 ) 213 depth = MultiValueNumberFilter( 214 field_name='_depth' 215 ) 216 children = MultiValueNumberFilter( 217 field_name='_children' 218 ) 219 mask_length = MultiValueNumberFilter( 220 field_name='prefix', 221 lookup_expr='net_mask_length' 222 ) 223 mask_length__gte = django_filters.NumberFilter( 224 field_name='prefix', 225 lookup_expr='net_mask_length__gte' 226 ) 227 mask_length__lte = django_filters.NumberFilter( 228 field_name='prefix', 229 lookup_expr='net_mask_length__lte' 230 ) 231 vrf_id = django_filters.ModelMultipleChoiceFilter( 232 queryset=VRF.objects.all(), 233 label='VRF', 234 ) 235 vrf = django_filters.ModelMultipleChoiceFilter( 236 field_name='vrf__rd', 237 queryset=VRF.objects.all(), 238 to_field_name='rd', 239 label='VRF (RD)', 240 ) 241 present_in_vrf_id = django_filters.ModelChoiceFilter( 242 queryset=VRF.objects.all(), 243 method='filter_present_in_vrf', 244 label='VRF' 245 ) 246 present_in_vrf = django_filters.ModelChoiceFilter( 247 queryset=VRF.objects.all(), 248 method='filter_present_in_vrf', 249 to_field_name='rd', 250 label='VRF (RD)', 251 ) 252 region_id = TreeNodeMultipleChoiceFilter( 253 queryset=Region.objects.all(), 254 field_name='site__region', 255 lookup_expr='in', 256 label='Region (ID)', 257 ) 258 region = TreeNodeMultipleChoiceFilter( 259 queryset=Region.objects.all(), 260 field_name='site__region', 261 lookup_expr='in', 262 to_field_name='slug', 263 label='Region (slug)', 264 ) 265 site_group_id = TreeNodeMultipleChoiceFilter( 266 queryset=SiteGroup.objects.all(), 267 field_name='site__group', 268 lookup_expr='in', 269 label='Site group (ID)', 270 ) 271 site_group = TreeNodeMultipleChoiceFilter( 272 queryset=SiteGroup.objects.all(), 273 field_name='site__group', 274 lookup_expr='in', 275 to_field_name='slug', 276 label='Site group (slug)', 277 ) 278 site_id = django_filters.ModelMultipleChoiceFilter( 279 queryset=Site.objects.all(), 280 label='Site (ID)', 281 ) 282 site = django_filters.ModelMultipleChoiceFilter( 283 field_name='site__slug', 284 queryset=Site.objects.all(), 285 to_field_name='slug', 286 label='Site (slug)', 287 ) 288 vlan_id = django_filters.ModelMultipleChoiceFilter( 289 queryset=VLAN.objects.all(), 290 label='VLAN (ID)', 291 ) 292 vlan_vid = django_filters.NumberFilter( 293 field_name='vlan__vid', 294 label='VLAN number (1-4095)', 295 ) 296 role_id = django_filters.ModelMultipleChoiceFilter( 297 queryset=Role.objects.all(), 298 label='Role (ID)', 299 ) 300 role = django_filters.ModelMultipleChoiceFilter( 301 field_name='role__slug', 302 queryset=Role.objects.all(), 303 to_field_name='slug', 304 label='Role (slug)', 305 ) 306 status = django_filters.MultipleChoiceFilter( 307 choices=PrefixStatusChoices, 308 null_value=None 309 ) 310 tag = TagFilter() 311 312 class Meta: 313 model = Prefix 314 fields = ['id', 'is_pool', 'mark_utilized'] 315 316 def search(self, queryset, name, value): 317 if not value.strip(): 318 return queryset 319 qs_filter = Q(description__icontains=value) 320 try: 321 prefix = str(netaddr.IPNetwork(value.strip()).cidr) 322 qs_filter |= Q(prefix__net_contains_or_equals=prefix) 323 except (AddrFormatError, ValueError): 324 pass 325 return queryset.filter(qs_filter) 326 327 def filter_prefix(self, queryset, name, value): 328 query_values = [] 329 for v in value: 330 try: 331 query_values.append(netaddr.IPNetwork(v)) 332 except (AddrFormatError, ValueError): 333 pass 334 return queryset.filter(prefix__in=query_values) 335 336 def search_within(self, queryset, name, value): 337 value = value.strip() 338 if not value: 339 return queryset 340 try: 341 query = str(netaddr.IPNetwork(value).cidr) 342 return queryset.filter(prefix__net_contained=query) 343 except (AddrFormatError, ValueError): 344 return queryset.none() 345 346 def search_within_include(self, queryset, name, value): 347 value = value.strip() 348 if not value: 349 return queryset 350 try: 351 query = str(netaddr.IPNetwork(value).cidr) 352 return queryset.filter(prefix__net_contained_or_equal=query) 353 except (AddrFormatError, ValueError): 354 return queryset.none() 355 356 def search_contains(self, queryset, name, value): 357 value = value.strip() 358 if not value: 359 return queryset 360 try: 361 # Searching by prefix 362 if '/' in value: 363 return queryset.filter(prefix__net_contains_or_equals=str(netaddr.IPNetwork(value).cidr)) 364 # Searching by IP address 365 else: 366 return queryset.filter(prefix__net_contains=str(netaddr.IPAddress(value))) 367 except (AddrFormatError, ValueError): 368 return queryset.none() 369 370 def filter_present_in_vrf(self, queryset, name, vrf): 371 if vrf is None: 372 return queryset.none 373 return queryset.filter( 374 Q(vrf=vrf) | 375 Q(vrf__export_targets__in=vrf.import_targets.all()) 376 ) 377 378 379class IPRangeFilterSet(TenancyFilterSet, PrimaryModelFilterSet): 380 q = django_filters.CharFilter( 381 method='search', 382 label='Search', 383 ) 384 family = django_filters.NumberFilter( 385 field_name='start_address', 386 lookup_expr='family' 387 ) 388 contains = django_filters.CharFilter( 389 method='search_contains', 390 label='Ranges which contain this prefix or IP', 391 ) 392 vrf_id = django_filters.ModelMultipleChoiceFilter( 393 queryset=VRF.objects.all(), 394 label='VRF', 395 ) 396 vrf = django_filters.ModelMultipleChoiceFilter( 397 field_name='vrf__rd', 398 queryset=VRF.objects.all(), 399 to_field_name='rd', 400 label='VRF (RD)', 401 ) 402 role_id = django_filters.ModelMultipleChoiceFilter( 403 queryset=Role.objects.all(), 404 label='Role (ID)', 405 ) 406 role = django_filters.ModelMultipleChoiceFilter( 407 field_name='role__slug', 408 queryset=Role.objects.all(), 409 to_field_name='slug', 410 label='Role (slug)', 411 ) 412 status = django_filters.MultipleChoiceFilter( 413 choices=IPRangeStatusChoices, 414 null_value=None 415 ) 416 tag = TagFilter() 417 418 class Meta: 419 model = IPRange 420 fields = ['id'] 421 422 def search(self, queryset, name, value): 423 if not value.strip(): 424 return queryset 425 qs_filter = Q(description__icontains=value) 426 try: 427 ipaddress = str(netaddr.IPNetwork(value.strip()).cidr) 428 qs_filter |= Q(start_address=ipaddress) 429 qs_filter |= Q(end_address=ipaddress) 430 except (AddrFormatError, ValueError): 431 pass 432 return queryset.filter(qs_filter) 433 434 def search_contains(self, queryset, name, value): 435 value = value.strip() 436 if not value: 437 return queryset 438 try: 439 # Strip mask 440 ipaddress = netaddr.IPNetwork(value) 441 return queryset.filter(start_address__lte=ipaddress, end_address__gte=ipaddress) 442 except (AddrFormatError, ValueError): 443 return queryset.none() 444 445 446class IPAddressFilterSet(PrimaryModelFilterSet, TenancyFilterSet): 447 q = django_filters.CharFilter( 448 method='search', 449 label='Search', 450 ) 451 family = django_filters.NumberFilter( 452 field_name='address', 453 lookup_expr='family' 454 ) 455 parent = django_filters.CharFilter( 456 method='search_by_parent', 457 label='Parent prefix', 458 ) 459 address = MultiValueCharFilter( 460 method='filter_address', 461 label='Address', 462 ) 463 mask_length = django_filters.NumberFilter( 464 method='filter_mask_length', 465 label='Mask length', 466 ) 467 vrf_id = django_filters.ModelMultipleChoiceFilter( 468 queryset=VRF.objects.all(), 469 label='VRF', 470 ) 471 vrf = django_filters.ModelMultipleChoiceFilter( 472 field_name='vrf__rd', 473 queryset=VRF.objects.all(), 474 to_field_name='rd', 475 label='VRF (RD)', 476 ) 477 present_in_vrf_id = django_filters.ModelChoiceFilter( 478 queryset=VRF.objects.all(), 479 method='filter_present_in_vrf', 480 label='VRF' 481 ) 482 present_in_vrf = django_filters.ModelChoiceFilter( 483 queryset=VRF.objects.all(), 484 method='filter_present_in_vrf', 485 to_field_name='rd', 486 label='VRF (RD)', 487 ) 488 device = MultiValueCharFilter( 489 method='filter_device', 490 field_name='name', 491 label='Device (name)', 492 ) 493 device_id = MultiValueNumberFilter( 494 method='filter_device', 495 field_name='pk', 496 label='Device (ID)', 497 ) 498 virtual_machine = MultiValueCharFilter( 499 method='filter_virtual_machine', 500 field_name='name', 501 label='Virtual machine (name)', 502 ) 503 virtual_machine_id = MultiValueNumberFilter( 504 method='filter_virtual_machine', 505 field_name='pk', 506 label='Virtual machine (ID)', 507 ) 508 interface = django_filters.ModelMultipleChoiceFilter( 509 field_name='interface__name', 510 queryset=Interface.objects.all(), 511 to_field_name='name', 512 label='Interface (name)', 513 ) 514 interface_id = django_filters.ModelMultipleChoiceFilter( 515 field_name='interface', 516 queryset=Interface.objects.all(), 517 label='Interface (ID)', 518 ) 519 vminterface = django_filters.ModelMultipleChoiceFilter( 520 field_name='vminterface__name', 521 queryset=VMInterface.objects.all(), 522 to_field_name='name', 523 label='VM interface (name)', 524 ) 525 vminterface_id = django_filters.ModelMultipleChoiceFilter( 526 field_name='vminterface', 527 queryset=VMInterface.objects.all(), 528 label='VM interface (ID)', 529 ) 530 assigned_to_interface = django_filters.BooleanFilter( 531 method='_assigned_to_interface', 532 label='Is assigned to an interface', 533 ) 534 status = django_filters.MultipleChoiceFilter( 535 choices=IPAddressStatusChoices, 536 null_value=None 537 ) 538 role = django_filters.MultipleChoiceFilter( 539 choices=IPAddressRoleChoices 540 ) 541 tag = TagFilter() 542 543 class Meta: 544 model = IPAddress 545 fields = ['id', 'dns_name', 'description'] 546 547 def search(self, queryset, name, value): 548 if not value.strip(): 549 return queryset 550 qs_filter = ( 551 Q(dns_name__icontains=value) | 552 Q(description__icontains=value) | 553 Q(address__istartswith=value) 554 ) 555 return queryset.filter(qs_filter) 556 557 def search_by_parent(self, queryset, name, value): 558 value = value.strip() 559 if not value: 560 return queryset 561 try: 562 query = str(netaddr.IPNetwork(value.strip()).cidr) 563 return queryset.filter(address__net_host_contained=query) 564 except (AddrFormatError, ValueError): 565 return queryset.none() 566 567 def filter_address(self, queryset, name, value): 568 try: 569 return queryset.filter(address__net_in=value) 570 except ValidationError: 571 return queryset.none() 572 573 def filter_mask_length(self, queryset, name, value): 574 if not value: 575 return queryset 576 return queryset.filter(address__net_mask_length=value) 577 578 def filter_present_in_vrf(self, queryset, name, vrf): 579 if vrf is None: 580 return queryset.none 581 return queryset.filter( 582 Q(vrf=vrf) | 583 Q(vrf__export_targets__in=vrf.import_targets.all()) 584 ) 585 586 def filter_device(self, queryset, name, value): 587 devices = Device.objects.filter(**{'{}__in'.format(name): value}) 588 if not devices.exists(): 589 return queryset.none() 590 interface_ids = [] 591 for device in devices: 592 interface_ids.extend(device.vc_interfaces().values_list('id', flat=True)) 593 return queryset.filter( 594 interface__in=interface_ids 595 ) 596 597 def filter_virtual_machine(self, queryset, name, value): 598 virtual_machines = VirtualMachine.objects.filter(**{'{}__in'.format(name): value}) 599 if not virtual_machines.exists(): 600 return queryset.none() 601 interface_ids = [] 602 for vm in virtual_machines: 603 interface_ids.extend(vm.interfaces.values_list('id', flat=True)) 604 return queryset.filter( 605 vminterface__in=interface_ids 606 ) 607 608 def _assigned_to_interface(self, queryset, name, value): 609 return queryset.exclude(assigned_object_id__isnull=value) 610 611 612class VLANGroupFilterSet(OrganizationalModelFilterSet): 613 q = django_filters.CharFilter( 614 method='search', 615 label='Search', 616 ) 617 scope_type = ContentTypeFilter() 618 region = django_filters.NumberFilter( 619 method='filter_scope' 620 ) 621 sitegroup = django_filters.NumberFilter( 622 method='filter_scope' 623 ) 624 site = django_filters.NumberFilter( 625 method='filter_scope' 626 ) 627 location = django_filters.NumberFilter( 628 method='filter_scope' 629 ) 630 rack = django_filters.NumberFilter( 631 method='filter_scope' 632 ) 633 clustergroup = django_filters.NumberFilter( 634 method='filter_scope' 635 ) 636 cluster = django_filters.NumberFilter( 637 method='filter_scope' 638 ) 639 640 class Meta: 641 model = VLANGroup 642 fields = ['id', 'name', 'slug', 'description', 'scope_id'] 643 644 def search(self, queryset, name, value): 645 if not value.strip(): 646 return queryset 647 qs_filter = ( 648 Q(name__icontains=value) | 649 Q(description__icontains=value) 650 ) 651 return queryset.filter(qs_filter) 652 653 def filter_scope(self, queryset, name, value): 654 return queryset.filter( 655 scope_type=ContentType.objects.get(model=name), 656 scope_id=value 657 ) 658 659 660class VLANFilterSet(PrimaryModelFilterSet, TenancyFilterSet): 661 q = django_filters.CharFilter( 662 method='search', 663 label='Search', 664 ) 665 region_id = TreeNodeMultipleChoiceFilter( 666 queryset=Region.objects.all(), 667 field_name='site__region', 668 lookup_expr='in', 669 label='Region (ID)', 670 ) 671 region = TreeNodeMultipleChoiceFilter( 672 queryset=Region.objects.all(), 673 field_name='site__region', 674 lookup_expr='in', 675 to_field_name='slug', 676 label='Region (slug)', 677 ) 678 site_group_id = TreeNodeMultipleChoiceFilter( 679 queryset=SiteGroup.objects.all(), 680 field_name='site__group', 681 lookup_expr='in', 682 label='Site group (ID)', 683 ) 684 site_group = TreeNodeMultipleChoiceFilter( 685 queryset=SiteGroup.objects.all(), 686 field_name='site__group', 687 lookup_expr='in', 688 to_field_name='slug', 689 label='Site group (slug)', 690 ) 691 site_id = django_filters.ModelMultipleChoiceFilter( 692 queryset=Site.objects.all(), 693 label='Site (ID)', 694 ) 695 site = django_filters.ModelMultipleChoiceFilter( 696 field_name='site__slug', 697 queryset=Site.objects.all(), 698 to_field_name='slug', 699 label='Site (slug)', 700 ) 701 group_id = django_filters.ModelMultipleChoiceFilter( 702 queryset=VLANGroup.objects.all(), 703 label='Group (ID)', 704 ) 705 group = django_filters.ModelMultipleChoiceFilter( 706 field_name='group__slug', 707 queryset=VLANGroup.objects.all(), 708 to_field_name='slug', 709 label='Group', 710 ) 711 role_id = django_filters.ModelMultipleChoiceFilter( 712 queryset=Role.objects.all(), 713 label='Role (ID)', 714 ) 715 role = django_filters.ModelMultipleChoiceFilter( 716 field_name='role__slug', 717 queryset=Role.objects.all(), 718 to_field_name='slug', 719 label='Role (slug)', 720 ) 721 status = django_filters.MultipleChoiceFilter( 722 choices=VLANStatusChoices, 723 null_value=None 724 ) 725 available_on_device = django_filters.ModelChoiceFilter( 726 queryset=Device.objects.all(), 727 method='get_for_device' 728 ) 729 available_on_virtualmachine = django_filters.ModelChoiceFilter( 730 queryset=VirtualMachine.objects.all(), 731 method='get_for_virtualmachine' 732 ) 733 tag = TagFilter() 734 735 class Meta: 736 model = VLAN 737 fields = ['id', 'vid', 'name'] 738 739 def search(self, queryset, name, value): 740 if not value.strip(): 741 return queryset 742 qs_filter = Q(name__icontains=value) | Q(description__icontains=value) 743 try: 744 qs_filter |= Q(vid=int(value.strip())) 745 except ValueError: 746 pass 747 return queryset.filter(qs_filter) 748 749 def get_for_device(self, queryset, name, value): 750 return queryset.get_for_device(value) 751 752 def get_for_virtualmachine(self, queryset, name, value): 753 return queryset.get_for_virtualmachine(value) 754 755 756class ServiceFilterSet(PrimaryModelFilterSet): 757 q = django_filters.CharFilter( 758 method='search', 759 label='Search', 760 ) 761 device_id = django_filters.ModelMultipleChoiceFilter( 762 queryset=Device.objects.all(), 763 label='Device (ID)', 764 ) 765 device = django_filters.ModelMultipleChoiceFilter( 766 field_name='device__name', 767 queryset=Device.objects.all(), 768 to_field_name='name', 769 label='Device (name)', 770 ) 771 virtual_machine_id = django_filters.ModelMultipleChoiceFilter( 772 queryset=VirtualMachine.objects.all(), 773 label='Virtual machine (ID)', 774 ) 775 virtual_machine = django_filters.ModelMultipleChoiceFilter( 776 field_name='virtual_machine__name', 777 queryset=VirtualMachine.objects.all(), 778 to_field_name='name', 779 label='Virtual machine (name)', 780 ) 781 port = NumericArrayFilter( 782 field_name='ports', 783 lookup_expr='contains' 784 ) 785 tag = TagFilter() 786 787 class Meta: 788 model = Service 789 fields = ['id', 'name', 'protocol'] 790 791 def search(self, queryset, name, value): 792 if not value.strip(): 793 return queryset 794 qs_filter = Q(name__icontains=value) | Q(description__icontains=value) 795 return queryset.filter(qs_filter) 796