Commit 01abb06b authored by Ramesh Kumar's avatar Ramesh Kumar

Merge branch 'master' into TMSS-624

parents fd09019d 9eda93fe
......@@ -21,4 +21,4 @@ class Migration(migrations.Migration):
migrations.RunPython(populate_misc),
migrations.RunPython(populate_resources),
migrations.RunPython(populate_cycles),
migrations.RunPython(populate_projects) ]
migrations.RunPython(populate_projects)]
......@@ -3,6 +3,7 @@ include(PythonInstall)
set(_py_files
__init__.py
permissions.py
specification.py
scheduling.py
common.py
......
......@@ -9,6 +9,9 @@ from .common import NamedCommonPK, AbstractChoice
from django.db.models import ManyToManyField
from enum import Enum
from rest_framework.permissions import DjangoModelPermissions
#
# Project Permissions
#
......@@ -31,3 +34,20 @@ class ProjectPermission(NamedCommonPK):
POST = ManyToManyField('ProjectRole', related_name='can_POST', blank=True)
PATCH = ManyToManyField('ProjectRole', related_name='can_PATCH', blank=True)
DELETE = ManyToManyField('ProjectRole', related_name='can_DELETE', blank=True)
# todo: move to viewsets / merge with TMSSDjangoModelPermissions class
class TMSSBasePermissions(DjangoModelPermissions):
# This enforces permissions as "deny any" by default.
view_permissions = ['%(app_label)s.view_%(model_name)s']
perms_map = {
'GET': view_permissions,
'OPTIONS': view_permissions,
'HEAD': view_permissions,
'POST': DjangoModelPermissions.perms_map['POST'],
'PUT': DjangoModelPermissions.perms_map['PUT'],
'PATCH': DjangoModelPermissions.perms_map['PATCH'],
'DELETE': DjangoModelPermissions.perms_map['DELETE'],
}
......@@ -19,6 +19,24 @@ from collections import Counter
from django.utils.functional import cached_property
#
# Mixins
#
class ProjectPropertyMixin:
@cached_property
def project(self): # -> Project:
'''return the related project of this task
'''
if not hasattr(self, 'path_to_project'):
return TMSSException("Please define a 'path_to_project' attribute on the object for the ProjectPropertyMixin to function.")
obj = self
for attr in self.path_to_project.split('__'):
obj = getattr(obj, attr)
if attr == 'project':
return obj
#
# I/O
#
......@@ -270,6 +288,7 @@ class Project(NamedCommonPK):
project_category = ForeignKey('ProjectCategory', null=True, on_delete=PROTECT, help_text='Project category.')
period_category = ForeignKey('PeriodCategory', null=True, on_delete=PROTECT, help_text='Period category.')
auto_pin = BooleanField(default=False, help_text='True if the output_pinned flag of tasks in this project should be set True on creation.')
path_to_project = "project"
@cached_property
def duration(self) -> datetime.timedelta:
......@@ -277,6 +296,10 @@ class Project(NamedCommonPK):
'''
return self.relative_stop_time - self.relative_start_time
@cached_property
def project(self):
return self
class ProjectQuota(Model):
project = ForeignKey('Project', related_name="quota", on_delete=PROTECT, help_text='Project to wich this quota belongs.') # protected to avoid accidents
......
......@@ -17,16 +17,18 @@ class Migration(migrations.Migration):
import logging
logger = logging.getLogger(__name__)
import json
import os
import inspect
import re
from datetime import datetime, timezone
from lofar.sas.tmss.tmss.tmssapp import models
from lofar.sas.tmss.tmss.tmssapp import viewsets
from lofar.sas.tmss.tmss.tmssapp.models.specification import *
from lofar.sas.tmss.tmss.tmssapp.models.scheduling import *
from lofar.sas.tmss.tmss.tmssapp.models.permissions import *
from lofar.common import isTestEnvironment, isDevelopmentEnvironment
from concurrent.futures import ThreadPoolExecutor
from django.contrib.auth.models import Group, Permission
from django.contrib.auth.models import User, Group, Permission
from django.contrib.contenttypes.models import ContentType
working_dir = os.path.dirname(os.path.abspath(__file__))
......@@ -245,6 +247,8 @@ def populate_connectors():
def populate_permissions():
logger.info('Populating permissions')
perm = ProjectPermission.objects.create(name='taskdraft')
perm.GET.set([ProjectRole.objects.get(value='shared_support_user')])
perm.POST.set([ProjectRole.objects.get(value='shared_support_user')])
......@@ -254,7 +258,175 @@ def populate_permissions():
perm.GET.set([ProjectRole.objects.get(value='shared_support_user')])
perm.save()
perm = ProjectPermission.objects.create(name='project')
perm.GET.set([ProjectRole.objects.get(value='shared_support_user')])
perm.POST.set([ProjectRole.objects.get(value='shared_support_user')])
perm.save()
populate_system_permissions()
populate_system_roles()
populate_system_test_users()
def populate_system_permissions():
# For each viewset create custom permissions for extra actions.
for name, obj in inspect.getmembers(viewsets):
if inspect.isclass(obj):
try:
ct = ContentType.objects.get_for_model(obj.serializer_class.Meta.model)
extra_actions = obj.get_extra_actions()
if extra_actions:
for action in extra_actions:
codename = ("%s_%s" % (action.__name__, obj.serializer_class.Meta.model.__name__)).lower()
name = f'Can {action.__name__} {obj.serializer_class.Meta.model.__name__.lower()}'
Permission.objects.create(codename=codename, name=name, content_type=ct)
except:
pass
def populate_system_roles():
to_observer_group = Group.objects.create(name='TO observer')
sdco_support_group = Group.objects.create(name='SDCO support')
tmss_maintainer_group = Group.objects.create(name='TMSS Maintainer')
tmss_admin_group = Group.objects.create(name='TMSS Admin')
to_maintenance_group = Group.objects.create(name='TO maintenance')
to_user_group = Group.objects.create(name='TO user')
scientist_group = Group.objects.create(name='Scientist')
e_scientist_group = Group.objects.create(name='Scientist (Expert)')
guest_group = Group.objects.create(name='Guest')
lta_user_group = Group.objects.create(name='LTA User')
assign_system_permissions()
def assign_system_permissions():
'''
Assign system permission to each system role, accordingly.
'''
# Get system roles
to_observer_group = Group.objects.get(name='TO observer')
sdco_support_group = Group.objects.get(name='SDCO support')
tmss_maintainer_group = Group.objects.get(name='TMSS Maintainer')
tmss_admin_group = Group.objects.get(name='TMSS Admin')
to_maintenance_group = Group.objects.get(name='TO maintenance')
to_user_group = Group.objects.get(name='TO user')
scientist_group = Group.objects.get(name='Scientist')
e_scientist_group = Group.objects.get(name='Scientist (Expert)')
guest_group = Group.objects.get(name='Guest')
lta_user_group = Group.objects.get(name='LTA User')
# Existing tests rely on this # todo: adapt tests to match actual permissions
scientist_group.permissions.add(Permission.objects.get(codename='view_cycle'))
scientist_group.permissions.add(Permission.objects.get(codename='view_project'))
# Subtask model permissions
ct = ContentType.objects.get(model='subtask')
perm = Permission.objects.get(codename='view_subtask')
to_observer_group.permissions.add(perm)
sdco_support_group.permissions.add(perm)
tmss_maintainer_group.permissions.add(perm)
tmss_admin_group.permissions.add(perm)
to_observer_group.permissions.add(Permission.objects.get(codename='schedule_subtask'))
sdco_support_group.permissions.add(Permission.objects.get(codename='schedule_subtask'))
tmss_maintainer_group.permissions.add(Permission.objects.get(codename='schedule_subtask'))
tmss_admin_group.permissions.add(Permission.objects.get(codename='schedule_subtask'))
# TODO: This is needed for testing atm. The table does not specify which roles should have this permission for now.
for subtask_perm in ['get_progress_subtask', 'input_dataproducts_subtask', 'output_dataproducts_subtask',
'parset_subtask', 'predecessors_subtask', 'process_feedback_and_set_to_finished_if_complete_subtask',
'reprocess_raw_feedback_for_subtask_and_set_to_finished_if_complete_subtask', 'state_log_subtask',
'successors_subtask', 'task_log_subtask', 'transformed_output_dataproduct_subtask', 'unschedule_subtask' ]:
to_observer_group.permissions.add(Permission.objects.get(codename=subtask_perm))
# Template models permissions
for name, obj in inspect.getmembers(models):
if inspect.isclass(obj) and issubclass(obj, Template) and obj is not Template:
ct = ContentType.objects.get(model=name.lower())
perm_name = ''.join('_'.join(re.findall(r'[A-Z](?:[a-z]+|[A-Z]*(?=[A-Z]|$))', name)).lower())
perm = Permission.objects.get(codename=f'add_{name.lower()}')
to_observer_group.permissions.add(perm)
sdco_support_group.permissions.add(perm)
tmss_maintainer_group.permissions.add(perm)
tmss_admin_group.permissions.add(perm)
e_scientist_group.permissions.add(perm)
perm = Permission.objects.get(codename=f'view_{name.lower()}')
to_observer_group.permissions.add(perm)
sdco_support_group.permissions.add(perm)
tmss_maintainer_group.permissions.add(perm)
tmss_admin_group.permissions.add(perm)
e_scientist_group.permissions.add(perm)
perm = Permission.objects.get(codename=f'change_{name.lower()}')
to_observer_group.permissions.add(perm)
sdco_support_group.permissions.add(perm)
tmss_maintainer_group.permissions.add(perm)
tmss_admin_group.permissions.add(perm)
e_scientist_group.permissions.add(perm)
# SchedulingUnit models permissions
for template in ['schedulingunitdraft', 'schedulingunitblueprint']:
ct = ContentType.objects.get(model=template)
perm = Permission.objects.get(codename=f'add_{template}')
to_observer_group.permissions.add(perm)
sdco_support_group.permissions.add(perm)
tmss_maintainer_group.permissions.add(perm)
tmss_admin_group.permissions.add(perm)
to_user_group.permissions.add(perm)
e_scientist_group.permissions.add(perm) if template is 'schedulingunitdraft' else None
perm = Permission.objects.get(codename=f'view_{template}')
to_observer_group.permissions.add(perm)
sdco_support_group.permissions.add(perm)
tmss_maintainer_group.permissions.add(perm)
tmss_admin_group.permissions.add(perm)
to_user_group.permissions.add(perm)
# Project model permissions
ct = ContentType.objects.get(model='project')
perm = Permission.objects.get(codename='add_project')
to_observer_group.permissions.add(perm)
sdco_support_group.permissions.add(perm)
tmss_maintainer_group.permissions.add(perm)
tmss_admin_group.permissions.add(perm)
perm = Permission.objects.get(codename='view_project')
to_observer_group.permissions.add(perm)
sdco_support_group.permissions.add(perm)
tmss_maintainer_group.permissions.add(perm)
tmss_admin_group.permissions.add(perm)
to_user_group.permissions.add(perm)
perm = Permission.objects.get(codename='change_project')
to_observer_group.permissions.add(perm)
sdco_support_group.permissions.add(perm)
tmss_maintainer_group.permissions.add(perm)
tmss_admin_group.permissions.add(perm)
# User model permissions
ct = ContentType.objects.get(model='user')
perm = Permission.objects.get(codename='add_user')
to_observer_group.permissions.add(perm)
sdco_support_group.permissions.add(perm)
tmss_maintainer_group.permissions.add(perm)
tmss_admin_group.permissions.add(perm)
def populate_system_test_users():
# TODO: Set proper credentials (passwords at least).
to_observer_user = User.objects.create(username='to_observer', password='to_observer')
to_observer_user.groups.add(Group.objects.get(name='TO observer'))
sdco_support_user = User.objects.create(username='sdco_support', password='sdco_support')
sdco_support_user.groups.add(Group.objects.get(name='SDCO support'))
tmss_maintainer_user = User.objects.create(username='tmss_maintainer', password='tmss_maintainer')
tmss_maintainer_user.groups.add(Group.objects.get(name='TMSS Maintainer'))
tmss_admin_user = User.objects.create(username='tmss_admin', password='tmss_admin')
tmss_admin_user.groups.add(Group.objects.get(name='TMSS Admin'))
to_maintenance_user = User.objects.create(username='to_maintenance', password='to_maintenance')
to_maintenance_user.groups.add(Group.objects.get(name='TO maintenance'))
to_user = User.objects.create(username='to_user', password='to_user')
to_user.groups.add(Group.objects.get(name='TO user'))
scientist_user = User.objects.create(username='scientist', password='scientist')
scientist_user.groups.add(Group.objects.get(name='Scientist'))
e_scientist_user = User.objects.create(username='e_scientist', password='e_scientist')
e_scientist_user.groups.add(Group.objects.get(name='Scientist (Expert)'))
guest_user = User.objects.create(username='guest', password='guest')
guest_user.groups.add(Group.objects.get(name='Guest'))
lta_user = User.objects.create(username='lta_user', password='lta_user')
lta_user.groups.add(Group.objects.get(name='LTA User'))
......@@ -7,6 +7,7 @@ set(_py_files
specification.py
scheduling.py
permissions.py
project_permissions.py
)
python_install(${_py_files}
......
from .specification import *
from .scheduling import *
from .permissions import *
\ No newline at end of file
from .permissions import *
from .project_permissions import *
\ No newline at end of file
......@@ -17,12 +17,61 @@ from django.http import JsonResponse
from django.urls import reverse as revese_url
from rest_framework.decorators import action
from lofar.common import json_utils
from lofar.sas.tmss.tmss.tmssapp.viewsets.permissions import TMSSPermissions, IsProjectMemberFilterBackend
from lofar.sas.tmss.tmss.tmssapp.models import permissions
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import OrderingFilter
#class TMSSPermissionsMixin:
# def __init__(self, *args, **kwargs):
# self.permission_classes = (TMSSPermissions,)
# self.filter_backends = (IsProjectMemberFilterBackend,)
# self.extra_action_permission_classes = self._create_extra_action_permission_classes()
# super(TMSSPermissionsMixin, self).__init__(*args, **kwargs)
#
# # TODO: Cache this method to avoid redundancy and overhead.
# def _create_extra_action_permission_classes(self):
# extra_action_permission_classes = []
# extra_actions = [a.__name__ for a in self.get_extra_actions()]
# for ea in extra_actions: # Create permission classes
# permission_name = f'{ea}_{self.serializer_class.Meta.model.__name__.lower()}'
# permission_class_name = f'Can {ea} {self.serializer_class.Meta.model.__name__.lower()}'
# new_permission_class = type(f'{permission_class_name}', (permissions.TMSSBasePermissions,), {
# # TODO: Is it necessary to have both permissions and object permissions?
# # TODO: Find a way to use the "%(app_label)s." syntax.
# 'permission_name': permission_name,
# 'has_permission': lambda self, request, view: request.user.has_perm(f'tmssapp.{self.permission_name}'),
# 'has_object_permission': lambda self, request, view, obj: request.user.has_perm(f'tmssapp.{self.permission_name}'),
# })
# new_permission_class.__setattr__(self, 'permission_name', permission_name)
# extra_action_permission_classes.append({ea: new_permission_class},)
# return extra_action_permission_classes
#
# # TODO: Refactoring.
# def get_model_permissions(self):
# extra_actions = [a.__name__ for a in self.get_extra_actions()]
# if self.action in extra_actions:
# for ea_permission_class in self.extra_action_permission_classes:
# if ea_permission_class.get(self.action):
# return [permissions.TMSSBasePermissions, ea_permission_class.get(self.action),]
# else:
# return [permissions.TMSSBasePermissions,]
# else:
# return [permissions.TMSSBasePermissions, ]
#def get_permissions(self):
# self.get_extra_action_permission_classes()
# return super(TMSSPermissionsMixin, self).get_permissions()
class LOFARViewSet(viewsets.ModelViewSet):
"""
If you're using format suffixes, make sure to also include
the `format=None` keyword argument for each action.
"""
permission_classes = (TMSSPermissions,)
filter_backends = (DjangoFilterBackend, OrderingFilter, IsProjectMemberFilterBackend,)
@swagger_auto_schema(responses={403: 'forbidden'})
def list(self, request, **kwargs):
......@@ -52,7 +101,7 @@ class LOFARNestedViewSet(mixins.CreateModelMixin,
mixins.ListModelMixin,
#mixins.RetrieveModelMixin,
viewsets.GenericViewSet):
@swagger_auto_schema(responses={403: 'forbidden'})
def list(self, request, **kwargs):
return super(LOFARNestedViewSet, self).list(request, **kwargs)
......@@ -70,6 +119,7 @@ class LOFARCopyViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet):
def list(self, request, **kwargs):
return super(LOFARCopyViewSet, self).list(request, **kwargs)
"""
@swagger_auto_schema(responses={400: 'invalid specification', 403: 'forbidden'})
def create(self, request, **kwargs):
return super(LOFARCopyViewSet, self).create(request, **kwargs)
......
......@@ -4,8 +4,6 @@ This file contains permissions and filters that are used in the viewsets
from rest_framework import permissions as drf_permissions, filters as drf_filters
from .. import models
from .. import serializers
from .lofar_viewset import LOFARViewSet
from lofar.sas.tmss.tmss.exceptions import *
from django.core.exceptions import ObjectDoesNotExist
import logging
......@@ -13,18 +11,6 @@ logger = logging.getLogger(__name__)
logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', level=logging.INFO)
from django.urls import resolve
import urllib.parse
#
# ViewSets
#
class ProjectRoleViewSet(LOFARViewSet):
queryset = models.ProjectRole.objects.all()
serializer_class = serializers.ProjectRoleSerializer
class ProjectPermissionViewSet(LOFARViewSet):
queryset = models.ProjectPermission.objects.all()
serializer_class = serializers.ProjectPermissionSerializer
#
......@@ -50,8 +36,8 @@ def get_project_roles_for_user(user):
try:
if user == models.User.objects.get(username='paulus'):
return ({'project': 'test_user_is_shared_support', 'role': 'shared_support_user'},
{'project': 'test_user_is_contact', 'role': 'contact_author'},
{'project': 'high', 'role': 'shared_support_user'})
{'project': 'test_user_is_contact', 'role': 'contact_author'})
#{'project': 'high', 'role': 'shared_support_user'})
except:
pass
......@@ -66,8 +52,8 @@ def get_project_roles_with_permission(permission_name, method='GET'):
# ...determine what project roles are allowed to perform the requested action
return getattr(project_permission, method).all()
except ObjectDoesNotExist:
raise TMSSException("This action was configured to enforce project permissions, but no project permission with name '%s' has been defined." % permission_name)
logger.error("This action was configured to enforce project permissions, but no project permission with name '%s' has been defined." % permission_name)
return []
class IsProjectMember(drf_permissions.DjangoObjectPermissions):
"""
......@@ -109,13 +95,25 @@ class IsProjectMember(drf_permissions.DjangoObjectPermissions):
# check whether the related project of this object is one that the user has permission to see
for project_role in user_project_roles:
if project_role['project'] == obj.project.name and \
models.ProjectRole.objects.get(value=project_role['role']) in permitted_project_roles:
logger.info('user=%s is permitted to access object=%s' % (request.user, obj))
logger.info('### IsProjectMember.has_object_permission %s %s True' % (request._request, request.method))
return True
logger.info('User=%s is not permitted to access object=%s with related project=%s since it requires one of project_roles=%s' % (request.user, obj, obj.project, permitted_project_roles))
if hasattr(obj, 'project'):
related_project = obj.project
if project_role['project'] == obj.project.name and \
models.ProjectRole.objects.get(value=project_role['role']) in permitted_project_roles:
logger.info('user=%s is permitted to access object=%s' % (request.user, obj))
logger.info('### IsProjectMember.has_object_permission %s %s True' % (request._request, request.method))
return True
else:
related_project = None
logger.error("No project property on object %s, so cannot check project permission." % obj)
# todo: how to deal with objects that do not have a unique project associated to them?
# Do need users need the required role in all of them? Or just one?
# Also, we need to support lists in 'path_to_project' if we want to handle this.
if issubclass(type(obj), models.Template) and view.action == 'default':
# todo: review this. Why is the default action called in a GET of an object that has a template reference?
logger.warning("'%s' is a Template and action is '%s' so granting object access nonetheless." % (obj, view.action))
return True
logger.info('User=%s is not permitted to access object=%s with related project=%s since it requires one of project_roles=%s' % (request.user, obj, related_project, permitted_project_roles))
logger.info('### IsProjectMember.has_object_permission %s False' % (request._request))
return False
......@@ -124,15 +122,17 @@ class IsProjectMember(drf_permissions.DjangoObjectPermissions):
# We need to check the project name before the object is created, but project is an object property.
# Turning the path to the project into a (static) model attribute, allows us to use it both in the object
# property as well as here (where we did not create an object yet).
if request.method == 'POST' and request.data:
if view.action == 'create' and request.data:
obj = None
if view.serializer_class.Meta.model == models.Project:
return False # project creation solely depends on system role
for attr in view.serializer_class.Meta.model.path_to_project.split('__'):
if not obj:
# on first iteration, the referenced object needs to be resolved from POSTed FQDN
obj_ref = request.data[attr]
path = urllib.parse.urlparse(obj_ref).path
resolved_func, _, resolved_kwargs = resolve(path)
obj = resolved_func.cls().get_queryset().get(id=resolved_kwargs['pk'])
obj = resolved_func.cls().get_queryset().get(pk=resolved_kwargs['pk'])
else:
if attr == 'project':
# has_object_permission checks the project from obj, so we can just check project permission on
......@@ -141,14 +141,15 @@ class IsProjectMember(drf_permissions.DjangoObjectPermissions):
logger.info('### IsProjectMember.has_permission %s %s' % (request._request, p))
return p
obj = getattr(obj, attr)
if view.action == 'list':
logger.info('Allowing list action %s %s' % (request.method, view.action))
logger.info('### IsProjectMember.has_permission %s %s True' % (request._request, request.method))
return True
logger.info('Allowing empty POST or non-POST request %s %s' % (request.method, view.action))
logger.info('### IsProjectMember.has_permission %s %s True' % (request._request, request.method))
return True
pk = view.kwargs.get('pk', None)
if pk:
obj = view.serializer_class.Meta.model.objects.get(pk=pk)
p = self.has_object_permission(request, view, obj)
else:
p = super().has_permission(request, view)
logger.info('### IsProjectMember.has_permission %s %s' % (request._request, p))
return p
class IsProjectMemberOrReadOnly(IsProjectMember):
......@@ -184,7 +185,13 @@ class TMSSDjangoModelPermissions(drf_permissions.DjangoModelPermissions):
}
def has_permission(self, request, view):
p = super().has_permission(request, view)
extra_actions = [a.__name__ for a in view.get_extra_actions()]
if view.action in extra_actions:
permission_name = f'{view.action}_{view.serializer_class.Meta.model.__name__.lower()}'
logger.info('### TMSSDjangoModelPermissions checking extra permission %s %s' % (request._request, permission_name))
p = request.user.has_perm(f'tmssapp.{permission_name}')
else:
p = super().has_permission(request, view)
logger.info('### TMSSDjangoModelPermissions.has_permission %s %s' % (request._request, p))
return p
......@@ -194,15 +201,16 @@ class TMSSPermissions(drf_permissions.DjangoObjectPermissions):
Create custom permission class
Note: required because the composition using & and | in the permission_classes does not seem to work as it should.
"""
model_permissions = TMSSDjangoModelPermissions()
project_permissions = IsProjectMember()
model_permissions = TMSSDjangoModelPermissions()
def has_permission(self, request, view):
return (self.model_permissions.has_permission(request, view) or
self.project_permissions.has_permission(request, view)) and request.user.is_authenticated
def has_object_permission(self, request, view, obj):
return IsProjectMember().has_object_permission(request, view, obj) and request.user.is_authenticated
return (self.model_permissions.has_permission(request, view) or
self.project_permissions.has_object_permission(request, view, obj)) and request.user.is_authenticated
#
......@@ -256,9 +264,10 @@ class IsProjectMemberFilterBackend(drf_filters.BaseFilterBackend):
permitted_fetched_objects = []
not_permitted = [o for o in queryset if o not in permitted_fetched_objects]
logger.info('User=%s is not permitted to access objects=%s with related projects=%s' % (request.user, not_permitted, [o.project for o in not_permitted]))
logger.info('### User=%s is not permitted to access objects=%s with related projects=%s' % (request.user, not_permitted, [o.project for o in not_permitted if hasattr(o, 'project')]))
logger.info('### User=%s is permitted to access objects=%s with related projects=%s' % (request.user, permitted_fetched_objects, [o.project for o in permitted_fetched_objects if hasattr(o, 'project')]))
# we could return the list of objects, which seems to work if you don't touch the get_queryset.
# But are supposed to return a queryset instead, so we make a new one, even though we fetched already.
# I don't know, there must be a better way...
return queryset.filter(id__in=[o.id for o in permitted_fetched_objects])
return queryset.filter(pk__in=[o.pk for o in permitted_fetched_objects])
from .. import models
from .. import serializers
from .lofar_viewset import LOFARViewSet
#
# Project Permission ViewSets
#
class ProjectRoleViewSet(LOFARViewSet):
queryset = models.ProjectRole.objects.all()
serializer_class = serializers.ProjectRoleSerializer