@@ -883,7 +883,7 @@ def test_device_action_selection(self):
883883 self .assertNotContains (response , 'name="group"' )
884884 self .assertNotContains (response , 'name="location"' )
885885 with self .subTest ("devices of different organizations are refused" ):
886- response = self ._post_device_action (devices + [ device_org2 ])
886+ response = self ._post_device_action ([ devices [ 0 ], device_org2 ])
887887 self .assertRedirects (response , self .device_changelist_url )
888888 self .assertIn (
889889 "All devices must belong to the same organization" ,
@@ -904,6 +904,74 @@ def test_device_action_selection(self):
904904 self ._post_confirm (wizard ["token" ])
905905 batch = BatchCommand .objects .get ()
906906 self .assertEqual (set (batch .devices .all ()), set (devices ))
907+ with self .subTest ("a single device is announced in the singular" ):
908+ response = self ._post_device_action (devices [:1 ])
909+ self .assertContains (
910+ response , "The command will run on the device you selected."
911+ )
912+ self ._start_wizard (devices = self ._pk_list (devices [:1 ]))
913+ response = self .client .get (self .confirm_url )
914+ self .assertEqual (response .context ["targets_display" ], "1 selected device" )
915+
916+ def test_device_action_system_wide (self ):
917+ org = self ._get_org ()
918+ org2 = self ._create_org (name = "org2" , slug = "org2" )
919+ devices = [
920+ self ._create_device (
921+ name = f"device{ index } " ,
922+ mac_address = f"00:11:22:33:44:0{ index } " ,
923+ organization = org ,
924+ )
925+ for index in range (2 )
926+ ]
927+ device_org2 = self ._create_device (
928+ name = "device-org2" , mac_address = "00:11:22:33:44:09" , organization = org2
929+ )
930+ self ._login ()
931+ with self .subTest ("selecting every device is system wide" ):
932+ response = self ._post_device_action (
933+ devices + [device_org2 ], select_across = True
934+ )
935+ self .assertEqual (response .status_code , 200 )
936+ form = response .context ["form" ]
937+ self .assertEqual (form .device_ids , [])
938+ self .assertEqual (response .context ["device_count" ], 0 )
939+ self .assertIsNone (form .fields ["organization" ].initial )
940+ for field_name in ("organization" , "group" , "location" ):
941+ self .assertFalse (form .fields [field_name ].disabled )
942+ self .assertContains (response , "The command will run on all devices." )
943+ self .assertNotContains (response , 'name="organization"' )
944+ self .assertNotContains (response , 'name="group"' )
945+ self .assertNotContains (response , 'name="location"' )
946+ with self .subTest ("the excluded devices are left out of the batch" ):
947+ wizard = self ._start_wizard ()
948+ self .client .get (self .confirm_url )
949+ response = self ._post_confirm (wizard ["token" ], excluded = str (device_org2 .pk ))
950+ self .assertEqual (response .status_code , 302 )
951+ self .assertIn (
952+ "Mass command executed successfully." , self ._messages (response )
953+ )
954+ batch = BatchCommand .objects .get ()
955+ self .assertIsNone (batch .organization_id )
956+ self .assertIsNone (batch .group_id )
957+ self .assertIsNone (batch .location_id )
958+ self .assertEqual (set (batch .devices .all ()), set (devices ))
959+ with self .subTest ("a partial multi organization selection is refused" ):
960+ self ._create_device (
961+ name = "excluded-by-the-search" ,
962+ mac_address = "00:11:22:33:44:08" ,
963+ organization = org2 ,
964+ )
965+ response = self ._post_device_action (
966+ devices + [device_org2 ],
967+ select_across = True ,
968+ query = {"q" : "device" },
969+ )
970+ self .assertRedirects (response , f"{ self .device_changelist_url } ?q=device" )
971+ self .assertIn (
972+ "All devices must belong to the same organization" ,
973+ " " .join (self ._messages (response )),
974+ )
907975
908976 def test_device_action_permissions_and_scope (self ):
909977 org = self ._get_org ()
@@ -912,9 +980,10 @@ def test_device_action_permissions_and_scope(self):
912980 device2 = self ._create_device (
913981 name = "device2" , mac_address = "00:11:22:33:44:02" , organization = org2
914982 )
915- device_admin = admin .site ._registry [ Device ]
983+ device_admin = admin .site .get_model_admin ( Device )
916984 request = RequestFactory ().get (self .device_changelist_url )
917- with self .subTest ("the add permission is required" ):
985+ action_name = "execute_mass_command_admin_action"
986+ with self .subTest ("the device change permission is required" ):
918987 viewer = self ._create_operator (
919988 organizations = [org ], username = "viewer" , email = "viewer@test.com"
920989 )
@@ -923,13 +992,19 @@ def test_device_action_permissions_and_scope(self):
923992 Permission .objects .filter (codename = "view_batchcommand" )
924993 )
925994 request .user = viewer
926- self .assertFalse (device_admin .has_execute_mass_command_permission (request ))
927- self .assertNotIn ("execute_mass_command" , device_admin .get_actions (request ))
995+ self .assertNotIn (action_name , device_admin .get_actions (request ))
928996 with self .subTest ("the operator group can use the action" ):
929997 operator = self ._create_operator (organizations = [org ])
930998 request .user = operator
931- self .assertTrue (device_admin .has_execute_mass_command_permission (request ))
932- self .assertIn ("execute_mass_command" , device_admin .get_actions (request ))
999+ self .assertIn (action_name , device_admin .get_actions (request ))
1000+ with self .subTest ("the batch command add permission is enforced" ):
1001+ viewer .user_permissions .set (
1002+ Permission .objects .filter (
1003+ codename__in = ["view_device" , "change_device" , "view_batchcommand" ]
1004+ )
1005+ )
1006+ self .client .force_login (viewer )
1007+ self .assertEqual (self ._post_device_action ([device ]).status_code , 403 )
9331008 with self .subTest ("devices of unmanaged organizations are dropped" ):
9341009 self .client .force_login (operator )
9351010 response = self ._post_execute (devices = self ._pk_list ([device , device2 ]))
@@ -951,6 +1026,28 @@ def test_device_action_permissions_and_scope(self):
9511026 "All devices must belong to the same organization" ,
9521027 " " .join (form .errors ["__all__" ]),
9531028 )
1029+ with self .subTest ("selecting every device is not system wide for operators" ):
1030+ multi_operator = self ._create_operator (
1031+ organizations = [org , org2 ], username = "multi" , email = "multi@test.com"
1032+ )
1033+ self .client .force_login (multi_operator )
1034+ response = self ._post_device_action ([device , device2 ], select_across = True )
1035+ self .assertRedirects (response , self .device_changelist_url )
1036+ self .assertIn (
1037+ "All devices must belong to the same organization" ,
1038+ " " .join (self ._messages (response )),
1039+ )
1040+ with self .subTest ("an operator executes the devices it manages" ):
1041+ self .client .force_login (operator )
1042+ wizard = self ._start_wizard (devices = self ._pk_list ([device ]))
1043+ self .assertEqual (wizard ["device_ids" ], [str (device .pk )])
1044+ self .assertEqual (wizard ["organization_id" ], str (org .pk ))
1045+ self .client .get (self .confirm_url )
1046+ response = self ._post_confirm (wizard ["token" ])
1047+ self .assertEqual (response .status_code , 302 )
1048+ batch = BatchCommand .objects .get ()
1049+ self .assertEqual (batch .organization_id , org .pk )
1050+ self .assertEqual (set (batch .devices .all ()), {device })
9541051
9551052 def test_changelist_multitenancy (self ):
9561053 org = self ._get_org ()
0 commit comments