1- from django .db .models .signals import m2m_changed
1+ from django .db .models .signals import m2m_changed , pre_delete
22from django .dispatch import receiver
33from guardian .shortcuts import assign_perm , remove_perm
44
55from grandchallenge .algorithms .models import Job
6- from grandchallenge .components .models import ComponentInterfaceValue
6+ from grandchallenge .cases .models import Image
77
88
99@receiver (m2m_changed , sender = Job .inputs .through )
1010@receiver (m2m_changed , sender = Job .outputs .through )
11- def update_input_image_permissions (
11+ def update_view_image_permissions_on_job_io_change ( # noqa:C901
1212 sender , instance , action , reverse , model , pk_set , ** _
1313):
1414 """
@@ -21,56 +21,67 @@ def update_input_image_permissions(
2121 return
2222
2323 if sender ._meta .label_lower == "algorithms.job_inputs" :
24- forward_lookup = "inputs"
2524 reverse_lookup = "algorithms_jobs_as_input"
2625 elif sender ._meta .label_lower == "algorithms.job_outputs" :
27- forward_lookup = "outputs"
2826 reverse_lookup = "algorithms_jobs_as_output"
2927 else :
3028 raise RuntimeError ("m2m is only valid for Job inputs and outputs." )
3129
3230 if reverse :
33- component_interface_values = ComponentInterfaceValue .objects .filter (
34- pk = instance .pk , image__isnull = False
35- )
31+ images = Image .objects .filter (componentinterfacevalue__pk = instance .pk )
3632 if pk_set is None :
3733 # When using a _clear action, pk_set is None
3834 # https://docs.djangoproject.com/en/2.2/ref/signals/#m2m-changed
3935 jobs = getattr (instance , reverse_lookup ).all ()
4036 else :
4137 jobs = model .objects .filter (pk__in = pk_set )
38+
39+ jobs = jobs .prefetch_related ("viewer_groups" ).only ("viewer_groups" )
4240 else :
4341 jobs = [instance ]
42+
4443 if pk_set is None :
4544 # When using a _clear action, pk_set is None
4645 # https://docs.djangoproject.com/en/2.2/ref/signals/#m2m-changed
47- component_interface_values = getattr (
48- instance , forward_lookup
49- ). filter ( image__isnull = False )
46+ images = Image . objects . filter (
47+ ** { f"componentinterfacevalue__ { reverse_lookup } __in" : jobs }
48+ )
5049 else :
51- component_interface_values = model .objects .filter (
52- pk__in = pk_set , image__isnull = False
50+ images = Image .objects .filter (
51+ componentinterfacevalue__pk__in = pk_set
5352 )
5453
55- _update_image_permissions (
56- jobs = jobs ,
57- component_interface_values = component_interface_values ,
58- exclude_jobs = action == "pre_clear" ,
59- )
54+ images = images .distinct ()
6055
56+ if action == "post_add" :
57+ for job in jobs :
58+ for group in job .viewer_groups .all ():
59+ assign_perm ("view_image" , group , images )
60+
61+ elif action in {"post_remove" , "pre_clear" }:
62+ exclude_jobs = jobs if action == "pre_clear" else None
63+
64+ for image in images :
65+ # We cannot remove image permissions directly as the groups
66+ # may have permissions through another object
67+ image .update_viewer_groups_permissions (exclude_jobs = exclude_jobs )
68+
69+ else :
70+ raise NotImplementedError
6171
62- def _update_image_permissions (
63- * , jobs , component_interface_values , exclude_jobs : bool
64- ):
65- for civ in component_interface_values :
66- # image__isnull=False is used above so we know that civ.image exists
67- civ .image .update_viewer_groups_permissions (
68- exclude_jobs = jobs if exclude_jobs else None
69- )
72+
73+ def _get_images_for_jobs (* , jobs ):
74+ input_images = Image .objects .filter (
75+ componentinterfacevalue__algorithms_jobs_as_input__in = jobs
76+ )
77+ output_images = Image .objects .filter (
78+ componentinterfacevalue__algorithms_jobs_as_output__in = jobs
79+ )
80+ return input_images .union (output_images )
7081
7182
7283@receiver (m2m_changed , sender = Job .viewer_groups .through )
73- def update_group_permissions (
84+ def update_permissions_on_viewer_groups_change ( # noqa:C901
7485 * _ , instance , action , reverse , model , pk_set , ** __
7586):
7687 if action not in ["post_add" , "post_remove" , "pre_clear" ]:
@@ -90,23 +101,34 @@ def update_group_permissions(
90101 else :
91102 groups = model .objects .filter (pk__in = pk_set )
92103
93- operation = assign_perm if "add" in action else remove_perm
104+ images = _get_images_for_jobs ( jobs = jobs )
94105
95- for job in jobs :
106+ if action == "post_add" :
96107 for group in groups :
97- operation ("view_job" , group , job )
108+ assign_perm ("view_job" , group , jobs )
109+ assign_perm ("view_image" , group , images )
98110
99- queryset = ComponentInterfaceValue .objects .filter (
100- image__isnull = False
101- ).select_related ("image" )
111+ elif action in {"post_remove" , "pre_clear" }:
112+ for group in groups :
113+ for job in jobs :
114+ remove_perm ("view_job" , group , job )
102115
103- input_civs = queryset .filter (algorithms_jobs_as_input__in = jobs )
104- output_civs = queryset .filter (algorithms_jobs_as_output__in = jobs )
116+ exclude_jobs = jobs if action == "pre_clear" else None
105117
106- component_interface_values = {* input_civs , * output_civs }
118+ for image in images :
119+ # We cannot remove image permissions directly as the groups
120+ # may have permissions through another object
121+ image .update_viewer_groups_permissions (exclude_jobs = exclude_jobs )
107122
108- _update_image_permissions (
109- jobs = jobs ,
110- component_interface_values = component_interface_values ,
111- exclude_jobs = action == "pre_clear" ,
112- )
123+ else :
124+ raise NotImplementedError
125+
126+
127+ @receiver (pre_delete , sender = Job )
128+ def update_view_image_permissions_on_job_deletion (* _ , instance : Job , ** __ ):
129+ jobs = [instance ]
130+
131+ for image in _get_images_for_jobs (jobs = jobs ):
132+ # We cannot remove image permissions directly as the groups
133+ # may have permissions through another object
134+ image .update_viewer_groups_permissions (exclude_jobs = jobs )
0 commit comments