Skip to content

Commit 3d3fefa

Browse files
pandafynemesifier
andcommitted
[feature] Easier filtering of relation between subnet and vpn #705
- Added VPN server autocomplete filter in Subnet list - Added subnet and ip columns in VPN list - Added possibility to filter by subnet in VPN list Closes #705 Co-authored-by: Federico Capoano <f.capoano@openwisp.io>
1 parent 90b4427 commit 3d3fefa

5 files changed

Lines changed: 124 additions & 35 deletions

File tree

openwisp_controller/config/admin.py

Lines changed: 17 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,11 @@
3535
TimeReadonlyAdminMixin,
3636
UUIDAdmin,
3737
)
38-
from openwisp_utils.admin_theme.filters import AutocompleteFilter
3938

4039
from ..admin import MultitenantAdminMixin
4140
from . import settings as app_settings
4241
from .base.vpn import AbstractVpn
42+
from .filters import DeviceGroupFilter, GroupFilter, SubnetFilter, TemplatesFilter
4343
from .utils import send_file
4444
from .widgets import DeviceGroupJsonSchemaWidget, JsonSchemaWidget
4545

@@ -443,19 +443,6 @@ class ChangeDeviceGroupForm(forms.Form):
443443
)
444444

445445

446-
class TemplatesFilter(AutocompleteFilter):
447-
title = _('template')
448-
field_name = 'templates'
449-
parameter_name = 'config__templates'
450-
rel_model = Config
451-
452-
453-
class GroupFilter(AutocompleteFilter):
454-
title = _('group')
455-
field_name = 'group'
456-
parameter_name = 'group_id'
457-
458-
459446
class DeviceAdmin(MultitenantAdminMixin, BaseConfigAdmin, UUIDAdmin):
460447
recover_form_template = 'admin/config/device_recover_form.html'
461448
list_display = [
@@ -916,8 +903,22 @@ class VpnAdmin(
916903
MultitenantAdminMixin, BaseConfigAdmin, UUIDAdmin, SystemDefinedVariableMixin
917904
):
918905
form = VpnForm
919-
list_display = ['name', 'organization', 'backend', 'created', 'modified']
920-
list_filter = [('organization', MultitenantOrgFilter), 'backend', 'created']
906+
list_display = [
907+
'name',
908+
'organization',
909+
'backend',
910+
'subnet',
911+
'ip',
912+
'created',
913+
'modified',
914+
]
915+
list_select_related = ['subnet', 'ip']
916+
list_filter = [
917+
('organization', MultitenantOrgFilter),
918+
'backend',
919+
SubnetFilter,
920+
'created',
921+
]
921922
search_fields = ['id', 'name', 'host', 'key']
922923
readonly_fields = ['id', 'uuid', 'system_context']
923924
multitenant_shared_relations = ('ca', 'cert', 'subnet')
@@ -978,22 +979,6 @@ class Meta(BaseForm.Meta):
978979
}
979980

980981

981-
class DeviceGroupFilter(admin.SimpleListFilter):
982-
title = _('has devices?')
983-
parameter_name = 'empty'
984-
985-
def lookups(self, request, model_admin):
986-
return (
987-
('true', _('No')),
988-
('false', _('Yes')),
989-
)
990-
991-
def queryset(self, request, queryset):
992-
if self.value():
993-
return queryset.filter(device__isnull=self.value() == 'true').distinct()
994-
return queryset
995-
996-
997982
class DeviceGroupAdmin(MultitenantAdminMixin, BaseAdmin):
998983
change_form_template = 'admin/device_group/change_form.html'
999984
form = DeviceGroupForm
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from django.contrib import admin
2+
from django.utils.translation import gettext_lazy as _
3+
from swapper import load_model
4+
5+
from openwisp_utils.admin_theme.filters import AutocompleteFilter
6+
7+
Config = load_model('config', 'Config')
8+
9+
10+
class TemplatesFilter(AutocompleteFilter):
11+
title = _('template')
12+
field_name = 'templates'
13+
parameter_name = 'config__templates'
14+
rel_model = Config
15+
16+
17+
class GroupFilter(AutocompleteFilter):
18+
title = _('group')
19+
field_name = 'group'
20+
parameter_name = 'group_id'
21+
22+
23+
class DeviceGroupFilter(admin.SimpleListFilter):
24+
title = _('has devices?')
25+
parameter_name = 'empty'
26+
27+
def lookups(self, request, model_admin):
28+
return (
29+
('true', _('No')),
30+
('false', _('Yes')),
31+
)
32+
33+
def queryset(self, request, queryset):
34+
if self.value():
35+
return queryset.filter(device__isnull=self.value() == 'true').distinct()
36+
return queryset
37+
38+
39+
class SubnetFilter(AutocompleteFilter):
40+
title = _('Subnet')
41+
field_name = 'subnet'
42+
parameter_name = 'subnet_id'

openwisp_controller/subnet_division/admin.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
SubnetDivisionRuleFilter,
1717
SubnetFilter,
1818
SubnetListFilter,
19+
VpnFilter,
1920
)
2021

2122
SubnetDivisionRule = load_model('subnet_division', 'SubnetDivisionRule')
@@ -59,8 +60,9 @@ class Media:
5960
@admin.register(Subnet)
6061
class SubnetAdmin(BaseSubnetAdmin):
6162
list_filter = BaseSubnetAdmin.list_filter + [
62-
DeviceFilter,
6363
SubnetDivisionRuleFilter,
64+
VpnFilter,
65+
DeviceFilter,
6466
]
6567
inlines = [SubnetDivisionRuleInlineAdmin] + BaseSubnetAdmin.inlines
6668
list_display = BaseSubnetAdmin.list_display

openwisp_controller/subnet_division/filters.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@
33
from django.utils.translation import gettext_lazy as _
44
from swapper import load_model
55

6-
from openwisp_utils.admin_theme.filters import SimpleInputFilter
6+
from openwisp_utils.admin_theme.filters import AutocompleteFilter, SimpleInputFilter
77

88
from . import settings as app_settings
99

1010
SubnetDivisionIndex = load_model('subnet_division', 'SubnetDivisionIndex')
1111
SubnetDivisionRule = load_model('subnet_division', 'SubnetDivisionRule')
1212
Subnet = load_model('openwisp_ipam', 'Subnet')
13+
Vpn = load_model('config', 'Vpn')
1314

1415

1516
class SubnetDivisionRuleFilter(admin.SimpleListFilter):
@@ -61,6 +62,17 @@ def queryset(self, request, queryset):
6162
)
6263

6364

65+
class VpnFilter(AutocompleteFilter):
66+
"""
67+
Filters Subnet by VPN
68+
autocomplete on inverse relation
69+
"""
70+
71+
title = _('VPN Server')
72+
field_name = 'vpn'
73+
parameter_name = 'vpn'
74+
75+
6476
class SubnetListFilter(admin.RelatedFieldListFilter):
6577
def field_choices(self, field, request, model_admin):
6678
if app_settings.HIDE_GENERATED_SUBNETS and field.name == 'subnet':

openwisp_controller/subnet_division/tests/test_admin.py

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from django.urls import reverse
55
from swapper import load_model
66

7+
from openwisp_controller.config.tests.utils import TestWireguardVpnMixin
78
from openwisp_users.tests.utils import TestMultitenantAdminMixin
89

910
from .helpers import SubnetDivisionAdminTestMixin
@@ -13,7 +14,10 @@
1314

1415

1516
class TestSubnetAdmin(
16-
SubnetDivisionAdminTestMixin, TestMultitenantAdminMixin, TestCase
17+
SubnetDivisionAdminTestMixin,
18+
TestWireguardVpnMixin,
19+
TestMultitenantAdminMixin,
20+
TestCase,
1721
):
1822
ipam_label = 'openwisp_ipam'
1923
config_label = 'config'
@@ -78,6 +82,50 @@ def test_device_filter_mutitenancy(self):
7882
self.assertNotContains(response, self.config.device.name)
7983
self.assertContains(response, config2.device.name)
8084

85+
def test_vpn_filter(self):
86+
subnet_changelist = reverse(f'admin:{self.ipam_label}_subnet_changelist')
87+
org = self._get_org()
88+
subnet1 = self._create_subnet(
89+
name='Subnet 1', subnet='172.16.0.0/24', organization=org
90+
)
91+
subnet2 = self._create_subnet(
92+
name='Subnet 2', subnet='172.16.1.0/24', organization=org
93+
)
94+
vpn = self._create_wireguard_vpn(subnet=subnet1, organization=org)
95+
url = f'{subnet_changelist}?vpn={vpn.id}'
96+
response = self.client.get(url)
97+
self.assertContains(
98+
response,
99+
subnet1.name,
100+
)
101+
self.assertNotContains(response, self.master_subnet.name)
102+
self.assertNotContains(response, subnet2.name)
103+
104+
def test_vpn_filter_mutitenancy(self):
105+
subnet_changelist = reverse(f'admin:{self.ipam_label}_subnet_changelist')
106+
org1 = self._create_org(name='org1')
107+
org2 = self._create_org(name='org2')
108+
subnet1 = self._create_subnet(
109+
name='Subnet 1', subnet='172.16.0.0/24', organization=org1
110+
)
111+
subnet2 = self._create_subnet(
112+
name='Subnet 2', subnet='172.16.1.0/24', organization=org2
113+
)
114+
vpn1 = self._create_wireguard_vpn(subnet=subnet1, organization=org1)
115+
administrator = self._create_administrator([org2])
116+
self.client.logout()
117+
self.client.force_login(administrator)
118+
url = f'{subnet_changelist}?vpn={vpn1.id}'
119+
response = self.client.get(url)
120+
self.assertNotContains(
121+
response,
122+
subnet1.name,
123+
)
124+
self.assertNotContains(
125+
response,
126+
subnet2.name,
127+
)
128+
81129
@patch('openwisp_controller.subnet_division.settings.HIDE_GENERATED_SUBNETS', True)
82130
def test_hide_generated_subnets(self):
83131
with self.subTest('Test SubnetAdmin'):

0 commit comments

Comments
 (0)