Skip to content

Commit 8c3b1e8

Browse files
authored
Remove on commit signals for image permission updates (#4387)
See DIAGNijmegen/rse-grand-challenge-admin#663
1 parent 67c0733 commit 8c3b1e8

5 files changed

Lines changed: 222 additions & 153 deletions

File tree

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,61 @@
11
from django.db.models.signals import m2m_changed, post_save, pre_delete
2-
from django.db.transaction import on_commit
32
from django.dispatch import receiver
43

54
from grandchallenge.archives.models import ArchiveItem
65
from grandchallenge.cases.models import Image
76

87

98
@receiver(m2m_changed, sender=ArchiveItem.values.through)
10-
def update_permissions_on_archive_item_changed(
11-
instance, action, reverse, pk_set, **_
9+
def update_view_image_permissions_on_archive_item_values_change(
10+
*, instance, action, reverse, model, pk_set, **_
1211
):
1312
if action not in ["post_add", "post_remove", "pre_clear"]:
1413
# nothing to do for the other actions
1514
return
1615

1716
if reverse:
1817
images = Image.objects.filter(componentinterfacevalue__pk=instance.pk)
18+
19+
if pk_set is None:
20+
# When using a _clear action, pk_set is None
21+
# https://docs.djangoproject.com/en/2.2/ref/signals/#m2m-changed
22+
archive_items = instance.archive_items.all()
23+
else:
24+
archive_items = model.objects.filter(pk__in=pk_set)
25+
1926
else:
27+
archive_items = [instance]
28+
2029
if pk_set is None:
2130
# When using a _clear action, pk_set is None
2231
# https://docs.djangoproject.com/en/2.2/ref/signals/#m2m-changed
23-
images = [
24-
civ.image
25-
for civ in instance.values.filter(image__isnull=False)
26-
]
32+
images = Image.objects.filter(
33+
componentinterfacevalue__archive_items=instance
34+
)
2735
else:
2836
images = Image.objects.filter(
2937
componentinterfacevalue__pk__in=pk_set
3038
)
3139

32-
def update_permissions():
33-
for image in images:
34-
image.update_viewer_groups_permissions()
40+
exclude_archive_items = archive_items if action == "pre_clear" else None
3541

36-
on_commit(update_permissions)
42+
for image in images.distinct():
43+
image.update_viewer_groups_permissions(
44+
exclude_archive_items=exclude_archive_items
45+
)
3746

3847

3948
@receiver(pre_delete, sender=ArchiveItem)
4049
@receiver(post_save, sender=ArchiveItem)
41-
def update_view_image_permissions(*_, instance: ArchiveItem, **__):
42-
images = [civ.image for civ in instance.values.filter(image__isnull=False)]
43-
44-
def update_permissions():
45-
for image in images:
46-
image.update_viewer_groups_permissions()
47-
48-
on_commit(update_permissions)
50+
def update_view_image_permissions_on_archive_item_change(
51+
*, instance: ArchiveItem, signal, **__
52+
):
53+
images = Image.objects.filter(
54+
componentinterfacevalue__archive_items=instance
55+
)
56+
exclude_archive_items = [instance] if signal is pre_delete else None
57+
58+
for image in images.distinct():
59+
image.update_viewer_groups_permissions(
60+
exclude_archive_items=exclude_archive_items
61+
)

app/grandchallenge/cases/models.py

Lines changed: 108 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -669,24 +669,51 @@ def sitk_image(self):
669669

670670
return sitk_image
671671

672-
def update_viewer_groups_permissions( # noqa: C901
673-
self, *, exclude_jobs=None
672+
def update_viewer_groups_permissions(
673+
self,
674+
*,
675+
exclude_jobs=None,
676+
exclude_archive_items=None,
677+
exclude_display_sets=None,
674678
):
675-
"""
676-
Update the permissions for the algorithm jobs viewers groups to
677-
view this image.
679+
expected_groups = set()
678680

679-
Parameters
680-
----------
681-
exclude_jobs
682-
Exclude these results from being considered. This is useful
683-
when a many to many relationship is being cleared to remove this
684-
image from the results image set, and is used when the pre_clear
685-
signal is sent.
686-
"""
681+
expected_groups.update(
682+
self._get_expected_job_viewer_groups(exclude_jobs=exclude_jobs)
683+
)
684+
685+
expected_groups.update(
686+
self._get_expected_archive_item_viewer_groups(
687+
exclude_archive_items=exclude_archive_items
688+
)
689+
)
690+
691+
expected_groups.update(
692+
self._get_expected_display_set_viewer_groups(
693+
exclude_display_sets=exclude_display_sets
694+
)
695+
)
696+
697+
expected_groups.update(self._get_expected_reader_study_answer_groups())
698+
699+
current_groups = get_groups_with_perms(self, attach_perms=True)
700+
current_groups = {
701+
group
702+
for group, perms in current_groups.items()
703+
if "view_image" in perms
704+
}
705+
706+
groups_missing_perms = expected_groups - current_groups
707+
groups_with_extra_perms = current_groups - expected_groups
708+
709+
for g in groups_missing_perms:
710+
assign_perm("view_image", g, self)
711+
712+
for g in groups_with_extra_perms:
713+
remove_perm("view_image", g, self)
714+
715+
def _get_expected_job_viewer_groups(self, exclude_jobs):
687716
from grandchallenge.algorithms.models import Job
688-
from grandchallenge.archives.models import Archive
689-
from grandchallenge.reader_studies.models import ReaderStudy
690717

691718
expected_groups = set()
692719

@@ -709,49 +736,89 @@ def update_viewer_groups_permissions( # noqa: C901
709736
logger.error(error, exc_info=True)
710737
raise
711738

712-
for archive in Archive.objects.filter(
713-
items__values__image=self
714-
).select_related("editors_group", "uploaders_group", "users_group"):
739+
return expected_groups
740+
741+
def _get_expected_archive_item_viewer_groups(
742+
self, *, exclude_archive_items
743+
):
744+
from grandchallenge.archives.models import ArchiveItem
745+
746+
expected_groups = set()
747+
748+
archive_items_queryset = (
749+
ArchiveItem.objects.filter(values__image=self)
750+
.select_related(
751+
"archive__editors_group",
752+
"archive__uploaders_group",
753+
"archive__users_group",
754+
)
755+
.only(
756+
"archive__editors_group",
757+
"archive__uploaders_group",
758+
"archive__users_group",
759+
)
760+
)
761+
762+
if exclude_archive_items is not None:
763+
archive_items_queryset = archive_items_queryset.exclude(
764+
pk__in={ai.pk for ai in exclude_archive_items}
765+
)
766+
767+
for archive_item in archive_items_queryset:
715768
expected_groups.update(
716769
[
717-
archive.editors_group,
718-
archive.uploaders_group,
719-
archive.users_group,
770+
archive_item.archive.editors_group,
771+
archive_item.archive.uploaders_group,
772+
archive_item.archive.users_group,
720773
]
721774
)
722775

723-
for rs in ReaderStudy.objects.filter(
724-
display_sets__values__image=self
725-
).select_related("editors_group", "readers_group"):
776+
return expected_groups
777+
778+
def _get_expected_display_set_viewer_groups(self, *, exclude_display_sets):
779+
from grandchallenge.reader_studies.models import DisplaySet
780+
781+
expected_groups = set()
782+
783+
display_set_queryset = (
784+
DisplaySet.objects.filter(values__image=self)
785+
.select_related(
786+
"reader_study__editors_group",
787+
"reader_study__readers_group",
788+
)
789+
.only(
790+
"reader_study__editors_group",
791+
"reader_study__readers_group",
792+
)
793+
)
794+
795+
if exclude_display_sets is not None:
796+
display_set_queryset = display_set_queryset.exclude(
797+
pk__in={ds.pk for ds in exclude_display_sets}
798+
)
799+
800+
for display_set in display_set_queryset:
726801
expected_groups.update(
727802
[
728-
rs.editors_group,
729-
rs.readers_group,
803+
display_set.reader_study.editors_group,
804+
display_set.reader_study.readers_group,
730805
]
731806
)
732807

808+
return expected_groups
809+
810+
def _get_expected_reader_study_answer_groups(self):
733811
# Reader study editors for reader studies that have answers that
734812
# include this image.
813+
814+
expected_groups = set()
815+
735816
for answer in self.answer_set.select_related(
736817
"question__reader_study__editors_group"
737818
).all():
738819
expected_groups.add(answer.question.reader_study.editors_group)
739820

740-
current_groups = get_groups_with_perms(self, attach_perms=True)
741-
current_groups = {
742-
group
743-
for group, perms in current_groups.items()
744-
if "view_image" in perms
745-
}
746-
747-
groups_missing_perms = expected_groups - current_groups
748-
groups_with_extra_perms = current_groups - expected_groups
749-
750-
for g in groups_missing_perms:
751-
assign_perm("view_image", g, self)
752-
753-
for g in groups_with_extra_perms:
754-
remove_perm("view_image", g, self)
821+
return expected_groups
755822

756823
def assign_view_perm_to_creator(self):
757824
for answer in self.answer_set.all():

app/grandchallenge/reader_studies/signals.py

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,53 +5,66 @@
55
pre_delete,
66
pre_save,
77
)
8-
from django.db.transaction import on_commit
98
from django.dispatch import receiver
109

1110
from grandchallenge.cases.models import Image
1211
from grandchallenge.reader_studies.models import DisplaySet
1312

1413

1514
@receiver(m2m_changed, sender=DisplaySet.values.through)
16-
def update_permissions_on_display_set_changed(
17-
instance, action, reverse, pk_set, **_
15+
def update_view_image_permissions_on_display_set_values_change(
16+
*, instance, action, reverse, model, pk_set, **_
1817
):
1918
if action not in ["post_add", "post_remove", "pre_clear"]:
2019
# nothing to do for the other actions
2120
return
2221

2322
if reverse:
2423
images = Image.objects.filter(componentinterfacevalue__pk=instance.pk)
24+
25+
if pk_set is None:
26+
# When using a _clear action, pk_set is None
27+
# https://docs.djangoproject.com/en/2.2/ref/signals/#m2m-changed
28+
display_sets = instance.display_sets.all()
29+
else:
30+
display_sets = model.objects.filter(pk__in=pk_set)
31+
2532
else:
33+
display_sets = [instance]
34+
2635
if pk_set is None:
2736
# When using a _clear action, pk_set is None
2837
# https://docs.djangoproject.com/en/2.2/ref/signals/#m2m-changed
29-
images = [
30-
civ.image
31-
for civ in instance.values.filter(image__isnull=False)
32-
]
38+
images = Image.objects.filter(
39+
componentinterfacevalue__display_sets=instance
40+
)
3341
else:
3442
images = Image.objects.filter(
3543
componentinterfacevalue__pk__in=pk_set
3644
)
3745

38-
def update_permissions():
39-
for image in images:
40-
image.update_viewer_groups_permissions()
46+
exclude_display_sets = display_sets if action == "pre_clear" else None
4147

42-
on_commit(update_permissions)
48+
for image in images.distinct():
49+
image.update_viewer_groups_permissions(
50+
exclude_display_sets=exclude_display_sets
51+
)
4352

4453

4554
@receiver(pre_delete, sender=DisplaySet)
4655
@receiver(post_save, sender=DisplaySet)
47-
def update_view_image_permissions(*_, instance: DisplaySet, **__):
48-
images = [civ.image for civ in instance.values.filter(image__isnull=False)]
49-
50-
def update_permissions():
51-
for image in images:
52-
image.update_viewer_groups_permissions()
53-
54-
on_commit(update_permissions)
56+
def update_view_image_permissions_on_display_set_change(
57+
*, instance: DisplaySet, signal, **__
58+
):
59+
images = Image.objects.filter(
60+
componentinterfacevalue__display_sets=instance
61+
)
62+
exclude_display_sets = [instance] if signal is pre_delete else None
63+
64+
for image in images.distinct():
65+
image.update_viewer_groups_permissions(
66+
exclude_display_sets=exclude_display_sets
67+
)
5568

5669

5770
@receiver(m2m_changed, sender=DisplaySet.values.through)

0 commit comments

Comments
 (0)