Skip to content

Commit

Permalink
Permissions attribute for targets, filtering logic and migrations
Browse files Browse the repository at this point in the history
Adds a permissions field on the Target that allows for filtering prior
to doing row-level permissions for open/public targets. This
significantly improves performance for TOMs with large datasets that are
mostly open or public.

Included migrations should handle the transition transparently.

Todo:

[] Extend filtering logic beyond the TargetList view.
[] Update target creation logic away from the Public group.
[] Apply logic to non-target models?
[] Tests.
[] Clean up any remaining 'Public' group logic.
[] Update documentation.
  • Loading branch information
Fingel committed Feb 6, 2025
1 parent 08fed00 commit b8c6318
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 2 deletions.
9 changes: 9 additions & 0 deletions tom_targets/base_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,11 @@ class BaseTarget(models.Model):
('JPL_MAJOR_PLANET', 'JPL Major Planet')
)

class Permissions(models.TextChoices):
OPEN = 'OPEN'
PUBLIC = 'PUBLIC'
PRIVATE = 'PRIVATE'

name = models.CharField(
max_length=100, default='', verbose_name='Name', help_text='The name of this target e.g. Barnard\'s star.',
unique=True
Expand All @@ -306,6 +311,10 @@ class BaseTarget(models.Model):
auto_now=True, verbose_name='Last Modified',
help_text='The time which this target was changed in the TOM database.'
)
permissions = models.CharField(
max_length=100, default=Permissions.PUBLIC, choices=Permissions.choices,
help_text='The access level of this target, see the docs on public vs private targets.'
)
ra = models.FloatField(
null=True, blank=True, verbose_name='Right Ascension', help_text='Right Ascension, in degrees.'
)
Expand Down
18 changes: 18 additions & 0 deletions tom_targets/migrations/0024_basetarget_permissions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.18 on 2025-02-06 18:39

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('tom_targets', '0023_alter_basetarget_created'),
]

operations = [
migrations.AddField(
model_name='basetarget',
name='permissions',
field=models.CharField(choices=[('OPEN', 'Open'), ('PUBLIC', 'Public'), ('PRIVATE', 'Private')], default='PUBLIC', help_text='The acess level of this target, see the docs on public vs private targets.', max_length=100),
),
]
58 changes: 58 additions & 0 deletions tom_targets/migrations/0025_auto_20250206_2017.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Generated by Django 4.2.18 on 2025-02-06 20:17

from django.db import migrations
from django.conf import settings

def get_target_model():
try:
custom_class = settings.TARGET_MODEL_CLASS
return custom_class.split('.')[0], custom_class.split('.')[-1]
except AttributeError:
return 'tom_targets', 'BaseTarget'

def remove_public_group(apps, schema_editor):
target_app, target_model = get_target_model()
Group = apps.get_model('auth', 'Group')
Target = apps.get_model(target_app, target_model)
UserObjectPermission = apps.get_model('guardian', 'UserObjectPermission')
GroupObjectPermission = apps.get_model('guardian', 'GroupObjectPermission')

group = Group.objects.get(name='Public')

# Delete Target permissions for public group
GroupObjectPermission.objects.filter(group=group, content_type__model=target_model.lower()).delete()

# Any remaining permissions means target should be private
private_group_permissions = GroupObjectPermission.objects.filter(
content_type__model=target_model.lower()
)
private_user_permissions = UserObjectPermission.objects.filter(
content_type__model=target_model.lower()
)

# get a list of target ids that still have permissions
target_ids = set(
list(private_group_permissions.values_list('object_pk', flat=True)) \
+ list(private_user_permissions.values_list('object_pk', flat=True))
)

# Update targets to private
Target.objects.filter(pk__in=target_ids).update(permissions='PRIVATE')

# Delete public group
group.delete()

def set_all_to_public(apps, schema_editor):
target_app, target_model = get_target_model()
Target = apps.get_model(target_app, target_model)
Target.objects.update(permissions='PUBLIC')

class Migration(migrations.Migration):

dependencies = [
('tom_targets', '0024_basetarget_permissions'),
]

operations = [
migrations.RunPython(remove_public_group, set_all_to_public),
]
21 changes: 19 additions & 2 deletions tom_targets/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
logger.setLevel(logging.DEBUG)


class TargetListView(PermissionListMixin, FilterView):
class TargetListView(FilterView):
"""
View for listing targets in the TOM. Only shows targets that the user is authorized to view. Requires authorization.
"""
Expand All @@ -72,7 +72,6 @@ class TargetListView(PermissionListMixin, FilterView):
model = Target
filterset_class = TargetFilter
# Set app_name for Django-Guardian Permissions in case of Custom Target Model
permission_required = f'{Target._meta.app_label}.view_target'
ordering = ['-created']

def get_context_data(self, *args, **kwargs):
Expand All @@ -93,6 +92,24 @@ def get_context_data(self, *args, **kwargs):
context['query_string'] = self.request.META['QUERY_STRING']
return context

def get_queryset(self, *args, **kwargs):
qs = super().get_queryset(*args, **kwargs)

if self.request.user.is_authenticated:
if self.request.user.is_superuser:
# Do not filter the queryset by permissions at all
return qs
else:
# Exclude targets that are private except for those that the user has explicit permissions to view
private_targets = qs.filter(permissions=Target.Permissions.PRIVATE)
public_targets = qs.exclude(permissions=Target.Permissions.PRIVATE)
return public_targets | get_objects_for_user(self.request.user, f'{Target._meta.app_label}.view_target', private_targets)
else:
# Only allow open targets
return qs.exclude(permissions__in=[Target.Permissions.PUBLIC, Target.Permissions.PRIVATE])




class TargetNameSearchView(RedirectView):
"""
Expand Down

0 comments on commit b8c6318

Please sign in to comment.