"""REST API view tests for the projectroles app"""
import base64
import json
import pytz

from django.conf import settings
from django.core import mail
from django.forms.models import model_to_dict
from django.test import override_settings
from django.urls import reverse
from django.utils import timezone

from knox.models import AuthToken

from test_plus.test import APITestCase

from projectroles import views_api
from projectroles.models import (
    Project,
    Role,
    RoleAssignment,
    ProjectInvite,
    SODAR_CONSTANTS,
)
from projectroles.plugins import change_plugin_status, get_backend_api
from projectroles.remote_projects import RemoteProjectAPI
from projectroles.tests.test_models import (
    ProjectMixin,
    RoleAssignmentMixin,
    ProjectInviteMixin,
    RemoteSiteMixin,
    RemoteProjectMixin,
    AppSettingMixin,
)
from projectroles.tests.test_views import (
    TestViewsBase,
    PROJECT_TYPE_CATEGORY,
    PROJECT_TYPE_PROJECT,
    PROJECT_ROLE_OWNER,
    PROJECT_ROLE_DELEGATE,
    PROJECT_ROLE_CONTRIBUTOR,
    PROJECT_ROLE_GUEST,
    REMOTE_SITE_NAME,
    REMOTE_SITE_URL,
    SITE_MODE_SOURCE,
    SITE_MODE_TARGET,
    REMOTE_SITE_DESC,
    REMOTE_SITE_SECRET,
)
from projectroles.utils import build_secret


CORE_API_MEDIA_TYPE_INVALID = 'application/vnd.bihealth.invalid'
CORE_API_VERSION_INVALID = '9.9.9'

INVALID_UUID = '11111111-1111-1111-1111-111111111111'
NEW_CATEGORY_TITLE = 'New Category'
NEW_PROJECT_TITLE = 'New Project'
UPDATED_TITLE = 'Updated Title'
UPDATED_DESC = 'Updated description'
UPDATED_README = 'Updated readme'

INVITE_USER_EMAIL = 'new1@example.com'
INVITE_USER2_EMAIL = 'new2@example.com'
INVITE_MESSAGE = 'Message'


# Base Classes -----------------------------------------------------------------


class SODARAPIViewTestMixin:
    """
    Mixin for SODAR and SODAR Core API views with accept headers, knox token
    authorization and general helper methods.
    """

    # Default API header parameters are for external SODAR site APIs
    # Override these for testing SODAR Core API views
    media_type = settings.SODAR_API_MEDIA_TYPE
    api_version = settings.SODAR_API_DEFAULT_VERSION

    # Copied from Knox tests
    @classmethod
    def _get_basic_auth_header(cls, username, password):
        return (
            'Basic %s'
            % base64.b64encode(
                ('%s:%s' % (username, password)).encode('ascii')
            ).decode()
        )

    @classmethod
    def get_token(cls, user, full_result=False):
        """
        Get or create a knox token for a user.

        :param user: User object
        :param full_result: Return full result of AuthToken creation if True
        :return: Token string or AuthToken creation tuple
        """
        result = AuthToken.objects.create(user=user)
        return result if full_result else result[1]

    @classmethod
    def get_serialized_user(cls, user):
        """
        Return serialization for a user.

        :param user: User object
        :return: Dict
        """
        return {
            'email': user.email,
            'name': user.name,
            'sodar_uuid': str(user.sodar_uuid),
            'username': user.username,
        }

    @classmethod
    def get_drf_datetime(cls, obj_dt):
        """
        Return datetime in DRF compatible format.

        :param obj_dt: Object DateTime field
        :return: String
        """
        return timezone.localtime(
            obj_dt, pytz.timezone(settings.TIME_ZONE)
        ).isoformat()

    @classmethod
    def get_accept_header(
        cls,
        media_type=None,
        version=None,
    ):
        """
        Return version accept header based on the media type and version string.

        :param media_type: String (default = cls.media_type)
        :param version: String (default = cls.api_version)
        :return: Dict
        """
        if not media_type:
            media_type = cls.media_type

        if not version:
            version = cls.api_version

        return {'HTTP_ACCEPT': '{}; version={}'.format(media_type, version)}

    @classmethod
    def get_token_header(cls, token):
        """
        Return auth header based on token.

        :param token: Token string
        :return: Dict
        """
        return {'HTTP_AUTHORIZATION': 'token {}'.format(token)}

    def request_knox(
        self,
        url,
        method='GET',
        format='json',
        data=None,
        token=None,
        media_type=None,
        version=None,
        header=None,
    ):
        """
        Perform a HTTP request with Knox token auth.

        :param url: URL for the request
        :param method: Request method (string, default="GET")
        :param format: Request format (string, default="json")
        :param data: Optional data for request (dict)
        :param token: Knox token string (if None, use self.knox_token)
        :param media_type: String (default = cls.media_type)
        :param version: String (default = cls.api_version)
        :return: Response object
        """
        if not token:
            token = self.knox_token

        req_kwargs = {
            'format': format,
            **self.get_accept_header(media_type, version),
            **self.get_token_header(token),
        }

        if data:
            req_kwargs['data'] = data

        if header:
            req_kwargs.update(header)

        req_method = getattr(self.client, method.lower(), None)

        if not req_method:
            raise ValueError('Unsupported method "{}"'.format(method))

        return req_method(url, **req_kwargs)


class TestAPIViewsBase(
    ProjectMixin, RoleAssignmentMixin, SODARAPIViewTestMixin, APITestCase
):
    """Base API test view with knox authentication"""

    def setUp(self):
        # Show complete diff in case of failure
        self.maxDiff = None
        # Force disabling of taskflow plugin if it's available
        if get_backend_api('taskflow'):
            change_plugin_status(
                name='taskflow', status=1, plugin_type='backend'  # 0 = Disabled
            )

        # Init roles
        self.role_owner = Role.objects.get_or_create(name=PROJECT_ROLE_OWNER)[0]
        self.role_delegate = Role.objects.get_or_create(
            name=PROJECT_ROLE_DELEGATE
        )[0]
        self.role_contributor = Role.objects.get_or_create(
            name=PROJECT_ROLE_CONTRIBUTOR
        )[0]
        self.role_guest = Role.objects.get_or_create(name=PROJECT_ROLE_GUEST)[0]

        # Init superuser
        self.user = self.make_user('superuser')
        self.user.is_staff = True
        self.user.is_superuser = True
        self.user.save()

        # Set up category and project with owner role assignments
        self.category = self._make_project(
            'TestCategory', PROJECT_TYPE_CATEGORY, None
        )
        self.cat_owner_as = self._make_assignment(
            self.category, self.user, self.role_owner
        )
        self.project = self._make_project(
            'TestProject', PROJECT_TYPE_PROJECT, self.category
        )
        self.owner_as = self._make_assignment(
            self.project, self.user, self.role_owner
        )

        # Get knox token for self.user
        self.knox_token = self.get_token(self.user)


class TestCoreAPIViewsBase(TestAPIViewsBase):
    """Override of TestAPIViewsBase to be used with SODAR Core API views"""

    media_type = views_api.CORE_API_MEDIA_TYPE
    api_version = views_api.CORE_API_DEFAULT_VERSION


# Tests ------------------------------------------------------------------------


class TestProjectListAPIView(TestCoreAPIViewsBase):
    """Tests for ProjectListAPIView"""

    def test_get(self):
        """Test ProjectListAPIView get() as project owner"""
        url = reverse('projectroles:api_project_list')
        response = self.request_knox(url)

        # Assert response
        self.assertEqual(response.status_code, 200)
        response_data = json.loads(response.content)
        self.assertEqual(len(response_data), 2)
        expected = [
            {
                'title': self.category.title,
                'type': self.category.type,
                'parent': None,
                'description': self.category.description,
                'readme': '',
                'public_guest_access': False,
                'submit_status': self.category.submit_status,
                'roles': {
                    str(self.cat_owner_as.sodar_uuid): {
                        'user': {
                            'username': self.user.username,
                            'name': self.user.name,
                            'email': self.user.email,
                            'sodar_uuid': str(self.user.sodar_uuid),
                        },
                        'role': PROJECT_ROLE_OWNER,
                        'sodar_uuid': str(self.cat_owner_as.sodar_uuid),
                    }
                },
                'sodar_uuid': str(self.category.sodar_uuid),
            },
            {
                'title': self.project.title,
                'type': self.project.type,
                'parent': str(self.category.sodar_uuid),
                'description': self.project.description,
                'readme': '',
                'public_guest_access': False,
                'submit_status': self.project.submit_status,
                'roles': {
                    str(self.owner_as.sodar_uuid): {
                        'user': {
                            'username': self.user.username,
                            'name': self.user.name,
                            'email': self.user.email,
                            'sodar_uuid': str(self.user.sodar_uuid),
                        },
                        'role': PROJECT_ROLE_OWNER,
                        'sodar_uuid': str(self.owner_as.sodar_uuid),
                    }
                },
                'sodar_uuid': str(self.project.sodar_uuid),
            },
        ]
        self.assertEqual(response_data, expected)

    def test_get_no_roles(self):
        """Test ProjectListAPIView get() without roles"""
        user_no_roles = self.make_user('user_no_roles')
        url = reverse('projectroles:api_project_list')
        response = self.request_knox(url, token=self.get_token(user_no_roles))

        # Assert response
        self.assertEqual(response.status_code, 200)
        response_data = json.loads(response.content)
        self.assertEqual(len(response_data), 0)

    def test_get_limited_roles(self):
        """Test ProjectListAPIView get() with only one role"""
        user_no_roles = self.make_user('user_no_roles')
        self._make_assignment(
            self.project, user_no_roles, self.role_contributor
        )
        url = reverse('projectroles:api_project_list')
        response = self.request_knox(url, token=self.get_token(user_no_roles))

        # Assert response
        self.assertEqual(response.status_code, 200)
        response_data = json.loads(response.content)
        self.assertEqual(len(response_data), 1)


class TestProjectRetrieveAPIView(AppSettingMixin, TestCoreAPIViewsBase):
    """Tests for ProjectRetrieveAPIView"""

    def test_get_category(self):
        """Test ProjectRetrieveAPIView get() with a category"""
        url = reverse(
            'projectroles:api_project_retrieve',
            kwargs={'project': self.category.sodar_uuid},
        )
        response = self.request_knox(url)

        # Assert response
        self.assertEqual(response.status_code, 200)
        response_data = json.loads(response.content)
        expected = {
            'title': self.category.title,
            'type': self.category.type,
            'parent': None,
            'description': self.category.description,
            'readme': '',
            'public_guest_access': False,
            'submit_status': self.category.submit_status,
            'roles': {
                str(self.cat_owner_as.sodar_uuid): {
                    'user': {
                        'username': self.user.username,
                        'name': self.user.name,
                        'email': self.user.email,
                        'sodar_uuid': str(self.user.sodar_uuid),
                    },
                    'role': PROJECT_ROLE_OWNER,
                    'sodar_uuid': str(self.cat_owner_as.sodar_uuid),
                }
            },
            'sodar_uuid': str(self.category.sodar_uuid),
        }
        self.assertEqual(response_data, expected)

    def test_get_project(self):
        """Test ProjectRetrieveAPIView get() with a project"""
        url = reverse(
            'projectroles:api_project_retrieve',
            kwargs={'project': self.project.sodar_uuid},
        )
        response = self.request_knox(url)

        # Assert response
        self.assertEqual(response.status_code, 200)
        response_data = json.loads(response.content)
        expected = {
            'title': self.project.title,
            'type': self.project.type,
            'parent': str(self.category.sodar_uuid),
            'description': self.project.description,
            'readme': '',
            'public_guest_access': False,
            'submit_status': self.project.submit_status,
            'roles': {
                str(self.owner_as.sodar_uuid): {
                    'user': {
                        'username': self.user.username,
                        'name': self.user.name,
                        'email': self.user.email,
                        'sodar_uuid': str(self.user.sodar_uuid),
                    },
                    'role': PROJECT_ROLE_OWNER,
                    'sodar_uuid': str(self.owner_as.sodar_uuid),
                }
            },
            'sodar_uuid': str(self.project.sodar_uuid),
        }
        self.assertEqual(response_data, expected)

    def test_get_not_found(self):
        """Test ProjectRetrieveAPIView get() with an invalid UUID"""
        url = reverse(
            'projectroles:api_project_retrieve',
            kwargs={'project': INVALID_UUID},
        )
        response = self.request_knox(url)
        self.assertEqual(response.status_code, 404)


class TestIPAllowing(AppSettingMixin, TestCoreAPIViewsBase):
    """Tests for IP allowing settings using ProjectRetrieveAPIView"""

    def _setup_ip_allowing(self, ip_list, role_suffix):
        """Setup users and roles for IP allowing test"""
        # Create new user
        user = self.make_user(role_suffix)
        # Remove previously assigned owner role for superuser.
        superuser_as_owner_role = RoleAssignment.objects.get(
            project=self.project, user=self.user, role=self.role_owner
        )
        superuser_as_owner_role.delete()
        # Assign requested role to user
        user_as = self._make_assignment(
            self.project, user, getattr(self, 'role_' + role_suffix)
        )

        user_cat_as = RoleAssignment.objects.get(
            project=self.category, user=self.user, role=self.role_owner
        )
        if role_suffix == 'owner':
            user_cat_as.delete()
            user_cat_as = self._make_assignment(
                self.category, user, getattr(self, 'role_' + role_suffix)
            )

        # Init IP restrict setting
        self._make_setting(
            app_name='projectroles',
            name='ip_restrict',
            setting_type='BOOLEAN',
            value=True,
            project=self.project,
        )

        # Init IP allowlist setting
        self._make_setting(
            app_name='projectroles',
            name='ip_allowlist',
            setting_type='JSON',
            value=None,
            value_json=ip_list,
            project=self.project,
        )

        return user, user_as, user_cat_as

    def _get_project_ip_allowing(
        self, username, http_attribute, ip_list, blocked=None
    ):
        """Helper for IP allowing tests"""
        if blocked is None:
            raise Exception('Please set "blocked" argument (True/False)')

        user, user_as, user_cat_as = self._setup_ip_allowing(ip_list, username)
        url = reverse(
            'projectroles:api_project_retrieve',
            kwargs={'project': self.project.sodar_uuid},
        )
        header = {http_attribute: '192.168.1.1'}
        response = self.request_knox(
            url, token=self.get_token(user), header=header
        )

        if username == 'owner':
            role = PROJECT_ROLE_OWNER
        elif username == 'delegate':
            role = PROJECT_ROLE_DELEGATE
        elif username == 'contributor':
            role = PROJECT_ROLE_CONTRIBUTOR
        else:
            role = PROJECT_ROLE_GUEST

        if blocked:
            self.assertEqual(response.status_code, 403)
        else:
            expected = {
                'title': self.project.title,
                'type': self.project.type,
                'parent': str(self.category.sodar_uuid),
                'description': self.project.description,
                'readme': '',
                'public_guest_access': False,
                'submit_status': self.project.submit_status,
                'roles': {
                    str(user_as.sodar_uuid): {
                        'user': {
                            'username': user.username,
                            'name': user.name,
                            'email': user.email,
                            'sodar_uuid': str(user.sodar_uuid),
                        },
                        'role': role,
                        'sodar_uuid': str(user_as.sodar_uuid),
                    }
                },
                'sodar_uuid': str(self.project.sodar_uuid),
            }

            # Assert response
            self.assertEqual(response.status_code, 200)
            response_data = json.loads(response.content)
            self.assertEqual(response_data, expected)

    def test_http_x_forwarded_for_block_all_owner(self):
        self._get_project_ip_allowing(
            'owner', 'HTTP_X_FORWARDED_FOR', [], blocked=False
        )

    def test_http_x_forwarded_for_allow_ip_owner(self):
        self._get_project_ip_allowing(
            'owner', 'HTTP_X_FORWARDED_FOR', ['192.168.1.1'], blocked=False
        )

    def test_http_x_forwarded_for_block_all_delegate(self):
        self._get_project_ip_allowing(
            'delegate', 'HTTP_X_FORWARDED_FOR', [], blocked=False
        )

    def test_http_x_forwarded_for_allow_ip_delegate(self):
        self._get_project_ip_allowing(
            'delegate', 'HTTP_X_FORWARDED_FOR', ['192.168.1.1'], blocked=False
        )

    def test_http_x_forwarded_for_block_all_contributor(self):
        self._get_project_ip_allowing(
            'contributor', 'HTTP_X_FORWARDED_FOR', [], blocked=True
        )

    def test_http_x_forwarded_for_allow_ip_contributor(self):
        self._get_project_ip_allowing(
            'contributor',
            'HTTP_X_FORWARDED_FOR',
            ['192.168.1.1'],
            blocked=False,
        )

    def test_http_x_forwarded_for_block_all_guest(self):
        self._get_project_ip_allowing(
            'guest', 'HTTP_X_FORWARDED_FOR', [], blocked=True
        )

    def test_http_x_forwarded_for_allow_ip_guest(self):
        self._get_project_ip_allowing(
            'guest', 'HTTP_X_FORWARDED_FOR', ['192.168.1.1'], blocked=False
        )

    def test_x_forwarded_for_block_all_owner(self):
        self._get_project_ip_allowing(
            'owner', 'X_FORWARDED_FOR', [], blocked=False
        )

    def test_x_forwarded_for_allow_ip_owner(self):
        self._get_project_ip_allowing(
            'owner', 'X_FORWARDED_FOR', ['192.168.1.1'], blocked=False
        )

    def test_x_forwarded_for_block_all_delegate(self):
        self._get_project_ip_allowing(
            'delegate', 'X_FORWARDED_FOR', [], blocked=False
        )

    def test_x_forwarded_for_allow_ip_delegate(self):
        self._get_project_ip_allowing(
            'delegate', 'X_FORWARDED_FOR', ['192.168.1.1'], blocked=False
        )

    def test_x_forwarded_for_block_all_contributor(self):
        self._get_project_ip_allowing(
            'contributor', 'X_FORWARDED_FOR', [], blocked=True
        )

    def test_forwarded_for_allow_ip_contributor(self):
        self._get_project_ip_allowing(
            'contributor', 'X_FORWARDED_FOR', ['192.168.1.1'], blocked=False
        )

    def test_forwarded_for_block_all_guest(self):
        self._get_project_ip_allowing(
            'guest', 'X_FORWARDED_FOR', [], blocked=True
        )

    def test_forwarded_for_allow_ip_guest(self):
        self._get_project_ip_allowing(
            'guest', 'X_FORWARDED_FOR', ['192.168.1.1'], blocked=False
        )

    def test_forwarded_block_all_owner(self):
        self._get_project_ip_allowing('owner', 'FORWARDED', [], blocked=False)

    def test_forwarded_allow_ip_owner(self):
        self._get_project_ip_allowing(
            'owner', 'FORWARDED', ['192.168.1.1'], blocked=False
        )

    def test_forwarded_block_all_delegate(self):
        self._get_project_ip_allowing(
            'delegate', 'FORWARDED', [], blocked=False
        )

    def test_forwarded_allow_ip_delegate(self):
        self._get_project_ip_allowing(
            'delegate', 'FORWARDED', ['192.168.1.1'], blocked=False
        )

    def test_forwarded_block_all_contributor(self):
        self._get_project_ip_allowing(
            'contributor', 'FORWARDED', [], blocked=True
        )

    def test_forwarded_allow_ip_contributor(self):
        self._get_project_ip_allowing(
            'contributor', 'FORWARDED', ['192.168.1.1'], blocked=False
        )

    def test_forwarded_block_all_guest(self):
        self._get_project_ip_allowing('guest', 'FORWARDED', [], blocked=True)

    def test_forwarded_allow_ip_guest(self):
        self._get_project_ip_allowing(
            'guest', 'FORWARDED', ['192.168.1.1'], blocked=False
        )

    def test_remote_addr_block_all_owner(self):
        self._get_project_ip_allowing('owner', 'REMOTE_ADDR', [], blocked=False)

    def test_remote_addr_allow_ip_owner(self):
        self._get_project_ip_allowing(
            'owner', 'REMOTE_ADDR', ['192.168.1.1'], blocked=False
        )

    def test_remote_addr_block_all_delegate(self):
        self._get_project_ip_allowing(
            'delegate', 'REMOTE_ADDR', [], blocked=False
        )

    def test_remote_addr_allow_ip_delegate(self):
        self._get_project_ip_allowing(
            'delegate', 'REMOTE_ADDR', ['192.168.1.1'], blocked=False
        )

    def test_remote_addr_block_all_contributor(self):
        self._get_project_ip_allowing(
            'contributor', 'REMOTE_ADDR', [], blocked=True
        )

    def test_remote_addr_allow_ip_contributor(self):
        self._get_project_ip_allowing(
            'contributor', 'REMOTE_ADDR', ['192.168.1.1'], blocked=False
        )

    def test_remote_addr_block_all_guest(self):
        self._get_project_ip_allowing('guest', 'REMOTE_ADDR', [], blocked=True)

    def test_remote_addr_allow_ip_guest(self):
        self._get_project_ip_allowing(
            'guest', 'REMOTE_ADDR', ['192.168.1.1'], blocked=False
        )

    def test_remote_addr_allow_network_guest(self):
        self._get_project_ip_allowing(
            'guest', 'REMOTE_ADDR', ['192.168.1.0/24'], blocked=False
        )

    def test_remote_addr_block_not_in_allowlist_ip_guest(self):
        self._get_project_ip_allowing(
            'guest', 'REMOTE_ADDR', ['192.168.1.2'], blocked=True
        )

    def test_remote_addr_block_not_in_allowlist_network_guest(
        self,
    ):
        self._get_project_ip_allowing(
            'guest', 'REMOTE_ADDR', ['192.168.2.0/24'], blocked=True
        )


class TestProjectCreateAPIView(
    RemoteSiteMixin, RemoteProjectMixin, TestCoreAPIViewsBase
):
    """Tests for ProjectCreateAPIView"""

    def test_create_category(self):
        """Test creating a root category"""

        # Assert preconditions
        self.assertEqual(Project.objects.count(), 2)

        url = reverse('projectroles:api_project_create')
        post_data = {
            'title': NEW_CATEGORY_TITLE,
            'type': PROJECT_TYPE_CATEGORY,
            'parent': '',
            'description': 'description',
            'readme': 'readme',
            'public_guest_access': False,
            'owner': str(self.user.sodar_uuid),
        }
        response = self.request_knox(url, method='POST', data=post_data)

        # Assert response and project status
        self.assertEqual(response.status_code, 201, msg=response.content)
        self.assertEqual(Project.objects.count(), 3)

        # Assert object content
        new_category = Project.objects.get(title=NEW_CATEGORY_TITLE)
        model_dict = model_to_dict(new_category)
        model_dict['readme'] = model_dict['readme'].raw
        expected = {
            'id': new_category.pk,
            'title': new_category.title,
            'type': new_category.type,
            'parent': None,
            'description': new_category.description,
            'readme': new_category.readme.raw,
            'public_guest_access': False,
            'submit_status': SODAR_CONSTANTS['SUBMIT_STATUS_OK'],
            'full_title': new_category.title,
            'sodar_uuid': new_category.sodar_uuid,
        }
        self.assertEqual(model_dict, expected)

        # Assert role assignment
        self.assertEqual(
            RoleAssignment.objects.filter(
                project=new_category, user=self.user, role=self.role_owner
            ).count(),
            1,
        )

        # Assert API response
        expected = {
            'title': NEW_CATEGORY_TITLE,
            'type': PROJECT_TYPE_CATEGORY,
            'parent': None,
            'description': new_category.description,
            'readme': new_category.readme.raw,
            'public_guest_access': False,
            'sodar_uuid': str(new_category.sodar_uuid),
        }
        self.assertEqual(json.loads(response.content), expected)

    def test_create_category_nested(self):
        """Test creating a category under an existing category"""

        # Assert preconditions
        self.assertEqual(Project.objects.count(), 2)

        url = reverse('projectroles:api_project_create')
        post_data = {
            'title': NEW_CATEGORY_TITLE,
            'type': PROJECT_TYPE_CATEGORY,
            'parent': str(self.category.sodar_uuid),
            'description': 'description',
            'readme': 'readme',
            'public_guest_access': False,
            'owner': str(self.user.sodar_uuid),
        }
        response = self.request_knox(url, method='POST', data=post_data)

        # Assert response and project status
        self.assertEqual(response.status_code, 201, msg=response.content)
        self.assertEqual(Project.objects.count(), 3)

        # Assert object content
        new_category = Project.objects.get(title=NEW_CATEGORY_TITLE)
        model_dict = model_to_dict(new_category)
        model_dict['readme'] = model_dict['readme'].raw
        expected = {
            'id': new_category.pk,
            'title': new_category.title,
            'type': new_category.type,
            'parent': self.category.pk,
            'description': new_category.description,
            'readme': new_category.readme.raw,
            'public_guest_access': False,
            'submit_status': SODAR_CONSTANTS['SUBMIT_STATUS_OK'],
            'full_title': self.category.title + ' / ' + new_category.title,
            'sodar_uuid': new_category.sodar_uuid,
        }
        self.assertEqual(model_dict, expected)

        # Assert role assignment
        self.assertEqual(
            RoleAssignment.objects.filter(
                project=new_category, user=self.user, role=self.role_owner
            ).count(),
            1,
        )

        # Assert API response
        expected = {
            'title': NEW_CATEGORY_TITLE,
            'type': PROJECT_TYPE_CATEGORY,
            'parent': str(self.category.sodar_uuid),
            'description': new_category.description,
            'readme': new_category.readme.raw,
            'public_guest_access': False,
            'sodar_uuid': str(new_category.sodar_uuid),
        }
        self.assertEqual(json.loads(response.content), expected)

    def test_create_project(self):
        """Test creating a project under an existing category"""

        # Assert preconditions
        self.assertEqual(Project.objects.count(), 2)

        url = reverse('projectroles:api_project_create')
        post_data = {
            'title': NEW_PROJECT_TITLE,
            'type': PROJECT_TYPE_PROJECT,
            'parent': str(self.category.sodar_uuid),
            'description': 'description',
            'readme': 'readme',
            'public_guest_access': False,
            'owner': str(self.user.sodar_uuid),
        }
        response = self.request_knox(url, method='POST', data=post_data)

        # Assert response and project status
        self.assertEqual(response.status_code, 201, msg=response.content)
        self.assertEqual(Project.objects.count(), 3)

        # Assert object content
        new_project = Project.objects.get(title=NEW_PROJECT_TITLE)
        model_dict = model_to_dict(new_project)
        model_dict['readme'] = model_dict['readme'].raw
        expected = {
            'id': new_project.pk,
            'title': new_project.title,
            'type': new_project.type,
            'parent': self.category.pk,
            'description': new_project.description,
            'readme': new_project.readme.raw,
            'public_guest_access': False,
            'submit_status': SODAR_CONSTANTS['SUBMIT_STATUS_OK'],
            'full_title': self.category.title + ' / ' + new_project.title,
            'sodar_uuid': new_project.sodar_uuid,
        }
        self.assertEqual(model_dict, expected)

        # Assert role assignment
        self.assertEqual(
            RoleAssignment.objects.filter(
                project=new_project, user=self.user, role=self.role_owner
            ).count(),
            1,
        )

        # Assert API response
        expected = {
            'title': NEW_PROJECT_TITLE,
            'type': PROJECT_TYPE_PROJECT,
            'parent': str(self.category.sodar_uuid),
            'description': new_project.description,
            'readme': new_project.readme.raw,
            'public_guest_access': False,
            'sodar_uuid': str(new_project.sodar_uuid),
        }
        self.assertEqual(json.loads(response.content), expected)

    def test_create_project_root(self):
        """Test creating a project in root (should fail)"""

        # Assert preconditions
        self.assertEqual(Project.objects.count(), 2)

        url = reverse('projectroles:api_project_create')
        post_data = {
            'title': NEW_PROJECT_TITLE,
            'type': PROJECT_TYPE_PROJECT,
            'parent': None,
            'description': 'description',
            'readme': 'readme',
            'public_guest_access': False,
            'owner': str(self.user.sodar_uuid),
        }
        response = self.request_knox(url, method='POST', data=post_data)

        # Assert response and project status
        self.assertEqual(response.status_code, 400)
        self.assertEqual(Project.objects.count(), 2)

    @override_settings(PROJECTROLES_DISABLE_CATEGORIES=True)
    def test_create_project_disable_categories(self):
        """Test creating a project in root with disabled categories"""

        # Assert preconditions
        self.assertEqual(Project.objects.count(), 2)

        url = reverse('projectroles:api_project_create')
        post_data = {
            'title': NEW_PROJECT_TITLE,
            'type': PROJECT_TYPE_PROJECT,
            'parent': '',
            'description': 'description',
            'readme': 'readme',
            'public_guest_access': False,
            'owner': str(self.user.sodar_uuid),
        }
        response = self.request_knox(url, method='POST', data=post_data)

        # Assert response and project status
        self.assertEqual(response.status_code, 201, msg=response.content)
        self.assertEqual(Project.objects.count(), 3)

    def test_create_project_duplicate_title(self):
        """Test creating a project with a title already in category (should fail)"""

        # Assert preconditions
        self.assertEqual(Project.objects.count(), 2)

        url = reverse('projectroles:api_project_create')
        post_data = {
            'title': self.project.title,
            'type': PROJECT_TYPE_PROJECT,
            'parent': str(self.category.sodar_uuid),
            'description': 'description',
            'readme': 'readme',
            'public_guest_access': False,
            'owner': str(self.user.sodar_uuid),
        }
        response = self.request_knox(url, method='POST', data=post_data)

        # Assert response and project status
        self.assertEqual(response.status_code, 400)
        self.assertEqual(Project.objects.count(), 2)

    def test_create_project_unknown_user(self):
        """Test creating a project with a non-existent user (should fail)"""

        # Assert preconditions
        self.assertEqual(Project.objects.count(), 2)

        url = reverse('projectroles:api_project_create')
        post_data = {
            'title': NEW_PROJECT_TITLE,
            'type': PROJECT_TYPE_PROJECT,
            'parent': str(self.category.sodar_uuid),
            'description': 'description',
            'readme': 'readme',
            'public_guest_access': False,
            'owner': INVALID_UUID,
        }
        response = self.request_knox(url, method='POST', data=post_data)

        # Assert response and project status
        self.assertEqual(response.status_code, 400)
        self.assertEqual(Project.objects.count(), 2)

    def test_create_project_unknown_parent(self):
        """Test creating a project with a non-existent parent category (should fail)"""

        # Assert preconditions
        self.assertEqual(Project.objects.count(), 2)

        url = reverse('projectroles:api_project_create')
        post_data = {
            'title': NEW_PROJECT_TITLE,
            'type': PROJECT_TYPE_PROJECT,
            'parent': INVALID_UUID,
            'description': 'description',
            'readme': 'readme',
            'public_guest_access': False,
            'owner': str(self.user.sodar_uuid),
        }
        response = self.request_knox(url, method='POST', data=post_data)

        # Assert response and project status
        self.assertEqual(response.status_code, 400)
        self.assertEqual(Project.objects.count(), 2)

    def test_create_project_invalid_parent(self):
        """Test creating a project with a project as parent (should fail)"""

        # Assert preconditions
        self.assertEqual(Project.objects.count(), 2)

        url = reverse('projectroles:api_project_create')
        post_data = {
            'title': NEW_PROJECT_TITLE,
            'type': PROJECT_TYPE_PROJECT,
            'parent': str(self.project.sodar_uuid),
            'description': 'description',
            'readme': 'readme',
            'public_guest_access': False,
            'owner': str(self.user.sodar_uuid),
        }
        response = self.request_knox(url, method='POST', data=post_data)

        # Assert response and project status
        self.assertEqual(response.status_code, 400)
        self.assertEqual(Project.objects.count(), 2)

    @override_settings(PROJECTROLES_SITE_MODE=SITE_MODE_TARGET)
    def test_create_project_target_enabled(self):
        """Test creating a project as TARGET with target creation allowed"""

        # Assert preconditions
        self.assertEqual(Project.objects.count(), 2)

        url = reverse('projectroles:api_project_create')
        post_data = {
            'title': NEW_PROJECT_TITLE,
            'type': PROJECT_TYPE_PROJECT,
            'parent': str(self.category.sodar_uuid),
            'description': 'description',
            'readme': 'readme',
            'public_guest_access': False,
            'owner': str(self.user.sodar_uuid),
        }
        response = self.request_knox(url, method='POST', data=post_data)

        # Assert response and project status
        self.assertEqual(response.status_code, 201, msg=response.content)
        self.assertEqual(Project.objects.count(), 3)

    @override_settings(PROJECTROLES_SITE_MODE=SITE_MODE_TARGET)
    def test_create_project_target_remote(self):
        """Test creating a project as TARGET under a remote category (should fail)"""

        # Create source site
        source_site = self._make_site(
            name=REMOTE_SITE_NAME,
            url=REMOTE_SITE_URL,
            mode=SITE_MODE_SOURCE,
            description=REMOTE_SITE_DESC,
            secret=REMOTE_SITE_SECRET,
        )

        # Make category remote
        self._make_remote_project(
            project_uuid=self.category.sodar_uuid,
            project=self.category,
            site=source_site,
            level=SODAR_CONSTANTS['REMOTE_LEVEL_READ_ROLES'],
        )

        # Assert preconditions
        self.assertEqual(Project.objects.count(), 2)

        url = reverse('projectroles:api_project_create')
        post_data = {
            'title': NEW_PROJECT_TITLE,
            'type': PROJECT_TYPE_PROJECT,
            'parent': str(self.category.sodar_uuid),
            'description': 'description',
            'readme': 'readme',
            'public_guest_access': False,
            'owner': str(self.user.sodar_uuid),
        }
        response = self.request_knox(url, method='POST', data=post_data)

        # Assert response and project status
        self.assertEqual(response.status_code, 403, msg=response.content)
        self.assertEqual(Project.objects.count(), 2)

    @override_settings(
        PROJECTROLES_SITE_MODE=SITE_MODE_TARGET,
        PROJECTROLES_TARGET_CREATE=False,
    )
    def test_create_project_target_disabled(self):
        """Test creating a project as TARGET with target creation disallowed (should fail)"""

        # Assert preconditions
        self.assertEqual(Project.objects.count(), 2)

        url = reverse('projectroles:api_project_create')
        post_data = {
            'title': NEW_PROJECT_TITLE,
            'type': PROJECT_TYPE_PROJECT,
            'parent': str(self.category.sodar_uuid),
            'description': 'description',
            'readme': 'readme',
            'public_guest_access': False,
            'owner': str(self.user.sodar_uuid),
        }
        response = self.request_knox(url, method='POST', data=post_data)

        # Assert response and project status
        self.assertEqual(response.status_code, 403, msg=response.content)
        self.assertEqual(Project.objects.count(), 2)


class TestProjectUpdateAPIView(
    RemoteSiteMixin, RemoteProjectMixin, TestCoreAPIViewsBase
):
    """Tests for ProjectUpdateAPIView"""

    def test_put_category(self):
        """Test put() for category updating"""

        # Assert preconditions
        self.assertEqual(Project.objects.count(), 2)

        url = reverse(
            'projectroles:api_project_update',
            kwargs={'project': self.category.sodar_uuid},
        )
        put_data = {
            'title': UPDATED_TITLE,
            'type': PROJECT_TYPE_CATEGORY,
            'parent': '',
            'description': UPDATED_DESC,
            'readme': UPDATED_README,
            'public_guest_access': True,
            'owner': str(self.user.sodar_uuid),
        }
        response = self.request_knox(url, method='PUT', data=put_data)

        # Assert response and project status
        self.assertEqual(response.status_code, 200, msg=response.content)
        self.assertEqual(Project.objects.count(), 2)

        # Assert object content
        self.category.refresh_from_db()
        model_dict = model_to_dict(self.category)
        model_dict['readme'] = model_dict['readme'].raw
        expected = {
            'id': self.category.pk,
            'title': UPDATED_TITLE,
            'type': PROJECT_TYPE_CATEGORY,
            'parent': None,
            'description': UPDATED_DESC,
            'readme': UPDATED_README,
            'public_guest_access': True,
            'submit_status': SODAR_CONSTANTS['SUBMIT_STATUS_OK'],
            'full_title': UPDATED_TITLE,
            'sodar_uuid': self.category.sodar_uuid,
        }
        self.assertEqual(model_dict, expected)

        # Assert API response
        expected = {
            'title': UPDATED_TITLE,
            'type': PROJECT_TYPE_CATEGORY,
            'parent': None,
            'submit_status': SODAR_CONSTANTS['SUBMIT_STATUS_OK'],
            'description': UPDATED_DESC,
            'readme': UPDATED_README,
            'public_guest_access': True,
            'roles': {
                str(self.category.get_owner().sodar_uuid): {
                    'role': PROJECT_ROLE_OWNER,
                    'user': self.get_serialized_user(self.user),
                    'sodar_uuid': str(self.category.get_owner().sodar_uuid),
                }
            },
            'sodar_uuid': str(self.category.sodar_uuid),
        }
        self.assertEqual(json.loads(response.content), expected)

    def test_put_project(self):
        """Test put() for project updating"""

        # Assert preconditions
        self.assertEqual(Project.objects.count(), 2)

        url = reverse(
            'projectroles:api_project_update',
            kwargs={'project': self.project.sodar_uuid},
        )
        put_data = {
            'title': UPDATED_TITLE,
            'type': PROJECT_TYPE_PROJECT,
            'parent': str(self.category.sodar_uuid),
            'description': UPDATED_DESC,
            'readme': UPDATED_README,
            'public_guest_access': True,
            'owner': str(self.user.sodar_uuid),
        }
        response = self.request_knox(url, method='PUT', data=put_data)

        # Assert response and project status
        self.assertEqual(response.status_code, 200, msg=response.content)
        self.assertEqual(Project.objects.count(), 2)

        # Assert object content
        self.project.refresh_from_db()
        model_dict = model_to_dict(self.project)
        model_dict['readme'] = model_dict['readme'].raw
        expected = {
            'id': self.project.pk,
            'title': UPDATED_TITLE,
            'type': PROJECT_TYPE_PROJECT,
            'parent': self.category.pk,
            'description': UPDATED_DESC,
            'readme': UPDATED_README,
            'public_guest_access': True,
            'submit_status': SODAR_CONSTANTS['SUBMIT_STATUS_OK'],
            'full_title': self.category.title + ' / ' + UPDATED_TITLE,
            'sodar_uuid': self.project.sodar_uuid,
        }
        self.assertEqual(model_dict, expected)

        # Assert API response
        expected = {
            'title': UPDATED_TITLE,
            'type': PROJECT_TYPE_PROJECT,
            'parent': str(self.category.sodar_uuid),
            'submit_status': SODAR_CONSTANTS['SUBMIT_STATUS_OK'],
            'description': UPDATED_DESC,
            'readme': UPDATED_README,
            'public_guest_access': True,
            'roles': {
                str(self.project.get_owner().sodar_uuid): {
                    'role': PROJECT_ROLE_OWNER,
                    'user': self.get_serialized_user(self.user),
                    'sodar_uuid': str(self.project.get_owner().sodar_uuid),
                }
            },
            'sodar_uuid': str(self.project.sodar_uuid),
        }
        self.assertEqual(json.loads(response.content), expected)

    def test_patch_category(self):
        """Test patch() for updating category metadata"""

        # Assert preconditions
        self.assertEqual(Project.objects.count(), 2)

        url = reverse(
            'projectroles:api_project_update',
            kwargs={'project': self.category.sodar_uuid},
        )
        patch_data = {
            'title': UPDATED_TITLE,
            'description': UPDATED_DESC,
            'readme': UPDATED_README,
        }
        response = self.request_knox(url, method='PATCH', data=patch_data)

        # Assert response and project status
        self.assertEqual(response.status_code, 200, msg=response.content)
        self.assertEqual(Project.objects.count(), 2)

        # Assert object content
        self.category.refresh_from_db()
        model_dict = model_to_dict(self.category)
        model_dict['readme'] = model_dict['readme'].raw
        expected = {
            'id': self.category.pk,
            'title': UPDATED_TITLE,
            'type': PROJECT_TYPE_CATEGORY,
            'parent': None,
            'description': UPDATED_DESC,
            'readme': UPDATED_README,
            'public_guest_access': False,
            'submit_status': SODAR_CONSTANTS['SUBMIT_STATUS_OK'],
            'full_title': UPDATED_TITLE,
            'sodar_uuid': self.category.sodar_uuid,
        }
        self.assertEqual(model_dict, expected)

        # Assert role assignment
        self.assertEqual(self.category.get_owner().user, self.user)

        # Assert API response
        expected = {
            'title': UPDATED_TITLE,
            'type': PROJECT_TYPE_CATEGORY,
            'parent': None,
            'submit_status': SODAR_CONSTANTS['SUBMIT_STATUS_OK'],
            'description': UPDATED_DESC,
            'readme': UPDATED_README,
            'public_guest_access': False,
            'roles': {
                str(self.category.get_owner().sodar_uuid): {
                    'role': PROJECT_ROLE_OWNER,
                    'user': self.get_serialized_user(self.user),
                    'sodar_uuid': str(self.category.get_owner().sodar_uuid),
                }
            },
            'sodar_uuid': str(self.category.sodar_uuid),
        }
        self.assertEqual(json.loads(response.content), expected)

    def test_patch_project(self):
        """Test patch() for updating project metadata"""

        # Assert preconditions
        self.assertEqual(Project.objects.count(), 2)

        url = reverse(
            'projectroles:api_project_update',
            kwargs={'project': self.project.sodar_uuid},
        )
        patch_data = {
            'title': UPDATED_TITLE,
            'description': UPDATED_DESC,
            'readme': UPDATED_README,
            'public_guest_access': True,
        }
        response = self.request_knox(url, method='PATCH', data=patch_data)

        # Assert response and project status
        self.assertEqual(response.status_code, 200, msg=response.content)
        self.assertEqual(Project.objects.count(), 2)

        # Assert object content
        self.project.refresh_from_db()
        model_dict = model_to_dict(self.project)
        model_dict['readme'] = model_dict['readme'].raw
        expected = {
            'id': self.project.pk,
            'title': UPDATED_TITLE,
            'type': PROJECT_TYPE_PROJECT,
            'parent': self.category.pk,
            'description': UPDATED_DESC,
            'readme': UPDATED_README,
            'public_guest_access': True,
            'submit_status': SODAR_CONSTANTS['SUBMIT_STATUS_OK'],
            'full_title': self.category.title + ' / ' + UPDATED_TITLE,
            'sodar_uuid': self.project.sodar_uuid,
        }
        self.assertEqual(model_dict, expected)

        # Assert role assignment
        self.assertEqual(self.project.get_owner().user, self.user)

        # Assert API response
        expected = {
            'title': UPDATED_TITLE,
            'type': PROJECT_TYPE_PROJECT,
            'parent': str(self.category.sodar_uuid),
            'submit_status': SODAR_CONSTANTS['SUBMIT_STATUS_OK'],
            'description': UPDATED_DESC,
            'readme': UPDATED_README,
            'public_guest_access': True,
            'roles': {
                str(self.project.get_owner().sodar_uuid): {
                    'role': PROJECT_ROLE_OWNER,
                    'user': self.get_serialized_user(self.user),
                    'sodar_uuid': str(self.project.get_owner().sodar_uuid),
                }
            },
            'sodar_uuid': str(self.project.sodar_uuid),
        }
        self.assertEqual(json.loads(response.content), expected)

    def test_patch_project_owner(self):
        """Test patch() for updating project owner (should fail)"""
        new_owner = self.make_user('new_owner')

        url = reverse(
            'projectroles:api_project_update',
            kwargs={'project': self.project.sodar_uuid},
        )
        patch_data = {'owner': str(new_owner.sodar_uuid)}
        response = self.request_knox(url, method='PATCH', data=patch_data)

        # Assert response
        self.assertEqual(response.status_code, 400, msg=response.content)

    def test_patch_project_move(self):
        """Test patch() for moving project under a different category"""

        # Assert preconditions
        self.assertEqual(
            self.project.full_title,
            self.category.title + ' / ' + self.project.title,
        )

        new_category = self._make_project(
            'NewCategory', PROJECT_TYPE_CATEGORY, None
        )
        self._make_assignment(new_category, self.user, self.role_owner)
        url = reverse(
            'projectroles:api_project_update',
            kwargs={'project': self.project.sodar_uuid},
        )
        patch_data = {'parent': str(new_category.sodar_uuid)}
        response = self.request_knox(url, method='PATCH', data=patch_data)

        # Assert response
        self.assertEqual(response.status_code, 200, msg=response.content)

        # Assert object content
        self.project.refresh_from_db()
        model_dict = model_to_dict(self.project)
        self.assertEqual(model_dict['parent'], new_category.pk)

        # Assert role assignment
        self.assertEqual(self.project.get_owner().user, self.user)

        # Assert child project full title update
        self.assertEqual(
            self.project.full_title,
            new_category.title + ' / ' + self.project.title,
        )

        # Assert API response
        self.assertEqual(
            json.loads(response.content)['parent'], str(new_category.sodar_uuid)
        )

    def test_patch_project_move_unallowed(self):
        """Test patch() for moving project without permissions (should fail)"""

        new_category = self._make_project(
            'NewCategory', PROJECT_TYPE_CATEGORY, None
        )
        new_owner = self.make_user('new_owner')
        self._make_assignment(new_category, new_owner, self.role_owner)
        url = reverse(
            'projectroles:api_project_update',
            kwargs={'project': self.project.sodar_uuid},
        )
        patch_data = {'parent': str(new_category.sodar_uuid)}
        # Disable superuser status from self.user and perform request
        self.user.is_superuser = False
        self.user.save()
        response = self.request_knox(url, method='PATCH', data=patch_data)

        # Assert response
        self.assertEqual(response.status_code, 403, msg=response.content)

    def test_patch_project_move_root(self):
        """Test patch() for moving project without permissions (should fail)"""

        new_category = self._make_project(
            'NewCategory', PROJECT_TYPE_CATEGORY, None
        )
        new_owner = self.make_user('new_owner')
        self._make_assignment(new_category, new_owner, self.role_owner)
        url = reverse(
            'projectroles:api_project_update',
            kwargs={'project': self.project.sodar_uuid},
        )
        patch_data = {'parent': ''}
        response = self.request_knox(url, method='PATCH', data=patch_data)

        # Assert response
        self.assertEqual(response.status_code, 200, msg=response.content)

    def test_patch_project_move_root_unallowed(self):
        """Test patch() for moving project to root without permissions (should fail)"""

        new_category = self._make_project(
            'NewCategory', PROJECT_TYPE_CATEGORY, None
        )
        new_owner = self.make_user('new_owner')
        self._make_assignment(new_category, new_owner, self.role_owner)
        url = reverse(
            'projectroles:api_project_update',
            kwargs={'project': self.project.sodar_uuid},
        )
        patch_data = {'parent': ''}
        # Disable superuser status from self.user and perform request
        self.user.is_superuser = False
        self.user.save()
        response = self.request_knox(url, method='PATCH', data=patch_data)

        # Assert response
        self.assertEqual(response.status_code, 403, msg=response.content)

    def test_patch_project_move_child(self):
        """Test patch() for moving a category inside its child (should fail)"""

        new_category = self._make_project(
            'NewCategory', PROJECT_TYPE_CATEGORY, self.category
        )
        self._make_assignment(new_category, self.user, self.role_owner)
        url = reverse(
            'projectroles:api_project_update',
            kwargs={'project': self.category.sodar_uuid},
        )
        patch_data = {'parent': str(new_category.sodar_uuid)}
        response = self.request_knox(url, method='PATCH', data=patch_data)

        # Assert response
        self.assertEqual(response.status_code, 400, msg=response.content)

    def test_patch_project_type_change(self):
        """Test patch() with a changed project type (should fail)"""
        url = reverse(
            'projectroles:api_project_update',
            kwargs={'project': self.project.sodar_uuid},
        )
        patch_data = {'type': PROJECT_TYPE_CATEGORY}
        response = self.request_knox(url, method='PATCH', data=patch_data)

        # Assert response
        self.assertEqual(response.status_code, 400, msg=response.content)

    @override_settings(PROJECTROLES_SITE_MODE=SITE_MODE_TARGET)
    def test_patch_project_remote(self):
        """Test patch() for updating remote project metadata (should fail)"""

        # Create source site and remote project
        source_site = self._make_site(
            name=REMOTE_SITE_NAME,
            url=REMOTE_SITE_URL,
            mode=SITE_MODE_SOURCE,
            description=REMOTE_SITE_DESC,
            secret=REMOTE_SITE_SECRET,
        )
        self._make_remote_project(
            project_uuid=self.project.sodar_uuid,
            project=self.project,
            site=source_site,
            level=SODAR_CONSTANTS['REMOTE_LEVEL_READ_ROLES'],
        )

        url = reverse(
            'projectroles:api_project_update',
            kwargs={'project': self.project.sodar_uuid},
        )
        patch_data = {
            'title': UPDATED_TITLE,
            'description': UPDATED_DESC,
            'readme': UPDATED_README,
        }
        response = self.request_knox(url, method='PATCH', data=patch_data)

        # Assert response and project status
        self.assertEqual(response.status_code, 400, msg=response.content)


class TestRoleAssignmentCreateAPIView(
    RemoteSiteMixin, RemoteProjectMixin, TestCoreAPIViewsBase
):
    """Tests for RoleAssignmentCreateAPIView"""

    def setUp(self):
        super().setUp()
        self.assign_user = self.make_user('assign_user')

    def test_create_contributor(self):
        """Test creating a contributor role for user"""

        # Assert preconditions
        self.assertEqual(
            RoleAssignment.objects.filter(project=self.project).count(), 1
        )

        url = reverse(
            'projectroles:api_role_create',
            kwargs={'project': self.project.sodar_uuid},
        )
        post_data = {
            'role': PROJECT_ROLE_CONTRIBUTOR,
            'user': str(self.assign_user.sodar_uuid),
        }
        response = self.request_knox(url, method='POST', data=post_data)

        # Assert response and role status
        self.assertEqual(response.status_code, 201, msg=response.content)
        self.assertEqual(
            RoleAssignment.objects.filter(project=self.project).count(), 2
        )

        # Assert object
        role_as = RoleAssignment.objects.filter(
            project=self.project,
            role=self.role_contributor,
            user=self.assign_user,
        ).first()
        self.assertIsNotNone(role_as)

        # Assert API response
        expected = {
            'project': str(self.project.sodar_uuid),
            'role': PROJECT_ROLE_CONTRIBUTOR,
            'user': str(self.assign_user.sodar_uuid),
            'sodar_uuid': str(role_as.sodar_uuid),
        }
        self.assertEqual(json.loads(response.content), expected)

    def test_create_owner(self):
        """Test creating an owner role (should fail)"""

        url = reverse(
            'projectroles:api_role_create',
            kwargs={'project': self.project.sodar_uuid},
        )
        post_data = {
            'role': PROJECT_ROLE_OWNER,
            'user': str(self.assign_user.sodar_uuid),
        }
        response = self.request_knox(url, method='POST', data=post_data)

        # Assert response
        self.assertEqual(response.status_code, 400, msg=response.content)

    def test_create_delegate(self):
        """Test creating a delegate role for user as owner"""

        # Disable superuser status from self.user
        self.user.is_superuser = False
        self.user.save()

        # Assert preconditions
        self.assertEqual(
            RoleAssignment.objects.filter(project=self.project).count(), 1
        )

        url = reverse(
            'projectroles:api_role_create',
            kwargs={'project': self.project.sodar_uuid},
        )
        post_data = {
            'role': PROJECT_ROLE_DELEGATE,
            'user': str(self.assign_user.sodar_uuid),
        }
        response = self.request_knox(url, method='POST', data=post_data)

        # Assert response and project status
        self.assertEqual(response.status_code, 201, msg=response.content)
        self.assertEqual(
            RoleAssignment.objects.filter(project=self.project).count(), 2
        )

        # Assert object
        role_as = RoleAssignment.objects.filter(
            project=self.project, role=self.role_delegate, user=self.assign_user
        ).first()
        self.assertIsNotNone(role_as)

    def test_create_delegate_unauthorized(self):
        """Test creating a delegate role without authorization (should fail)"""

        # Create new user and grant delegate role
        new_user = self.make_user('new_user')
        self._make_assignment(self.project, new_user, self.role_contributor)
        new_user_token = self.get_token(new_user)

        url = reverse(
            'projectroles:api_role_create',
            kwargs={'project': self.project.sodar_uuid},
        )
        post_data = {
            'role': PROJECT_ROLE_DELEGATE,
            'user': str(self.assign_user.sodar_uuid),
        }
        response = self.request_knox(
            url, method='POST', data=post_data, token=new_user_token
        )

        # Assert response
        self.assertEqual(response.status_code, 403, msg=response.content)

    def test_create_delegate_limit(self):
        """Test creating a delegate role with limit reached (should fail)"""

        # Create new user and grant delegate role
        new_user = self.make_user('new_user')
        self._make_assignment(self.project, new_user, self.role_delegate)

        url = reverse(
            'projectroles:api_role_create',
            kwargs={'project': self.project.sodar_uuid},
        )
        post_data = {
            'role': PROJECT_ROLE_DELEGATE,
            'user': str(self.assign_user.sodar_uuid),
        }

        # NOTE: Post as owner
        response = self.request_knox(url, method='POST', data=post_data)

        # Assert response
        self.assertEqual(response.status_code, 400, msg=response.content)

    def test_create_delegate_limit_inherit(self):
        """Test creating a delegate role existing role for inherited owner"""

        # Set up category owner
        new_user = self.make_user('new_user')
        self.cat_owner_as.user = new_user

        url = reverse(
            'projectroles:api_role_create',
            kwargs={'project': self.project.sodar_uuid},
        )
        post_data = {
            'role': PROJECT_ROLE_DELEGATE,
            'user': str(self.assign_user.sodar_uuid),
        }
        # NOTE: Post as owner
        response = self.request_knox(url, method='POST', data=post_data)

        # Assert response and project status
        self.assertEqual(response.status_code, 201, msg=response.content)
        self.assertEqual(
            RoleAssignment.objects.filter(project=self.project).count(), 2
        )
        # Assert object
        role_as = RoleAssignment.objects.filter(
            project=self.project, role=self.role_delegate, user=self.assign_user
        ).first()
        self.assertIsNotNone(role_as)

    def test_create_delegate_category(self):
        """Test creating a non-owner role for category"""

        url = reverse(
            'projectroles:api_role_create',
            kwargs={'project': self.category.sodar_uuid},
        )
        post_data = {
            'role': PROJECT_ROLE_DELEGATE,
            'user': str(self.assign_user.sodar_uuid),
        }
        response = self.request_knox(url, method='POST', data=post_data)

        # Assert response
        self.assertEqual(response.status_code, 201, msg=response.content)

    def test_create_role_existing(self):
        """Test creating a role for user already in the project"""

        # Assert preconditions
        self.assertEqual(
            RoleAssignment.objects.filter(project=self.project).count(), 1
        )

        url = reverse(
            'projectroles:api_role_create',
            kwargs={'project': self.project.sodar_uuid},
        )
        post_data = {
            'role': PROJECT_ROLE_CONTRIBUTOR,
            'user': str(self.assign_user.sodar_uuid),
        }
        response = self.request_knox(url, method='POST', data=post_data)

        # Assert response and role status
        self.assertEqual(response.status_code, 201, msg=response.content)
        self.assertEqual(
            RoleAssignment.objects.filter(project=self.project).count(), 2
        )

        # Post again
        post_data = {
            'role': PROJECT_ROLE_GUEST,
            'user': str(self.assign_user.sodar_uuid),
        }
        response = self.request_knox(url, method='POST', data=post_data)

        # Assert response and project status
        self.assertEqual(response.status_code, 400, msg=response.content)
        self.assertEqual(
            RoleAssignment.objects.filter(project=self.project).count(), 2
        )

    @override_settings(PROJECTROLES_SITE_MODE=SITE_MODE_TARGET)
    def test_create_remote(self):
        """Test creating a role for a remote project (should fail)"""

        # Create source site and remote project
        source_site = self._make_site(
            name=REMOTE_SITE_NAME,
            url=REMOTE_SITE_URL,
            mode=SITE_MODE_SOURCE,
            description=REMOTE_SITE_DESC,
            secret=REMOTE_SITE_SECRET,
        )
        self._make_remote_project(
            project_uuid=self.project.sodar_uuid,
            project=self.project,
            site=source_site,
            level=SODAR_CONSTANTS['REMOTE_LEVEL_READ_ROLES'],
        )

        # Assert preconditions
        self.assertEqual(
            RoleAssignment.objects.filter(project=self.project).count(), 1
        )

        url = reverse(
            'projectroles:api_role_create',
            kwargs={'project': self.project.sodar_uuid},
        )
        post_data = {
            'role': PROJECT_ROLE_CONTRIBUTOR,
            'user': str(self.assign_user.sodar_uuid),
        }
        response = self.request_knox(url, method='POST', data=post_data)

        # Assert response and role status
        self.assertEqual(response.status_code, 400, msg=response.content)
        self.assertEqual(
            RoleAssignment.objects.filter(project=self.project).count(), 1
        )


class TestRoleAssignmentUpdateAPIView(
    RemoteSiteMixin, RemoteProjectMixin, TestCoreAPIViewsBase
):
    """Tests for RoleAssignmentUpdateAPIView"""

    def setUp(self):
        super().setUp()
        self.assign_user = self.make_user('assign_user')
        self.update_as = self._make_assignment(
            self.project, self.assign_user, self.role_contributor
        )

    def test_put_role(self):
        """Test put() for role assignment updating"""

        # Assert preconditions
        self.assertEqual(RoleAssignment.objects.count(), 3)

        url = reverse(
            'projectroles:api_role_update',
            kwargs={'roleassignment': self.update_as.sodar_uuid},
        )
        put_data = {
            'role': PROJECT_ROLE_GUEST,
            'user': str(self.assign_user.sodar_uuid),
        }
        response = self.request_knox(url, method='PUT', data=put_data)

        # Assert response and role status
        self.assertEqual(response.status_code, 200, msg=response.content)
        self.assertEqual(RoleAssignment.objects.count(), 3)

        # Assert object content
        self.update_as.refresh_from_db()
        model_dict = model_to_dict(self.update_as)
        expected = {
            'id': self.update_as.pk,
            'project': self.project.pk,
            'role': self.role_guest.pk,
            'user': self.assign_user.pk,
            'sodar_uuid': self.update_as.sodar_uuid,
        }
        self.assertEqual(model_dict, expected)

        # Assert API response
        expected = {
            'project': str(self.project.sodar_uuid),
            'role': PROJECT_ROLE_GUEST,
            'user': str(self.assign_user.sodar_uuid),
            'sodar_uuid': str(self.update_as.sodar_uuid),
        }
        self.assertEqual(json.loads(response.content), expected)

    def test_put_delegate(self):
        """Test put() for delegate role assignment"""
        url = reverse(
            'projectroles:api_role_update',
            kwargs={'roleassignment': self.update_as.sodar_uuid},
        )
        put_data = {
            'role': PROJECT_ROLE_DELEGATE,
            'user': str(self.assign_user.sodar_uuid),
        }
        response = self.request_knox(url, method='PUT', data=put_data)

        # Assert response
        self.assertEqual(response.status_code, 200, msg=response.content)

        # Assert object content
        self.update_as.refresh_from_db()
        model_dict = model_to_dict(self.update_as)
        expected = {
            'id': self.update_as.pk,
            'project': self.project.pk,
            'role': self.role_delegate.pk,
            'user': self.assign_user.pk,
            'sodar_uuid': self.update_as.sodar_uuid,
        }
        self.assertEqual(model_dict, expected)

        # Assert API response
        expected = {
            'project': str(self.project.sodar_uuid),
            'role': PROJECT_ROLE_DELEGATE,
            'user': str(self.assign_user.sodar_uuid),
            'sodar_uuid': str(self.update_as.sodar_uuid),
        }
        self.assertEqual(json.loads(response.content), expected)

    def test_put_owner(self):
        """Test put() for owner role assignment (should fail)"""
        url = reverse(
            'projectroles:api_role_update',
            kwargs={'roleassignment': self.update_as.sodar_uuid},
        )
        put_data = {
            'role': PROJECT_ROLE_OWNER,
            'user': str(self.assign_user.sodar_uuid),
        }
        response = self.request_knox(url, method='PUT', data=put_data)

        # Assert response
        self.assertEqual(response.status_code, 400, msg=response.content)

    def test_put_change_user(self):
        """Test put() with a different user (should fail)"""
        new_user = self.make_user('new_user')

        url = reverse(
            'projectroles:api_role_update',
            kwargs={'roleassignment': self.update_as.sodar_uuid},
        )
        put_data = {
            'role': PROJECT_ROLE_GUEST,
            'user': str(new_user.sodar_uuid),
        }
        response = self.request_knox(url, method='PUT', data=put_data)

        # Assert response
        self.assertEqual(response.status_code, 400, msg=response.content)

    def test_patch_role(self):
        """Test patch() for role assignment updating"""

        # Assert preconditions
        self.assertEqual(RoleAssignment.objects.count(), 3)

        url = reverse(
            'projectroles:api_role_update',
            kwargs={'roleassignment': self.update_as.sodar_uuid},
        )
        patch_data = {'role': PROJECT_ROLE_GUEST}
        response = self.request_knox(url, method='PATCH', data=patch_data)

        # Assert response and role status
        self.assertEqual(response.status_code, 200, msg=response.content)
        self.assertEqual(RoleAssignment.objects.count(), 3)

        # Assert object content
        self.update_as.refresh_from_db()
        model_dict = model_to_dict(self.update_as)
        expected = {
            'id': self.update_as.pk,
            'project': self.project.pk,
            'role': self.role_guest.pk,
            'user': self.assign_user.pk,
            'sodar_uuid': self.update_as.sodar_uuid,
        }
        self.assertEqual(model_dict, expected)

        # Assert API response
        expected = {
            'project': str(self.project.sodar_uuid),
            'role': PROJECT_ROLE_GUEST,
            'user': str(self.assign_user.sodar_uuid),
            'sodar_uuid': str(self.update_as.sodar_uuid),
        }
        self.assertEqual(json.loads(response.content), expected)

    def test_patch_change_user(self):
        """Test patch() with a different user (should fail)"""
        new_user = self.make_user('new_user')

        url = reverse(
            'projectroles:api_role_update',
            kwargs={'roleassignment': self.update_as.sodar_uuid},
        )
        patch_data = {'user': str(new_user.sodar_uuid)}
        response = self.request_knox(url, method='PATCH', data=patch_data)

        # Assert response
        self.assertEqual(response.status_code, 400, msg=response.content)

    @override_settings(PROJECTROLES_SITE_MODE=SITE_MODE_TARGET)
    def test_patch_role_remote(self):
        """Test patch() for updating a role in a remote project (should fail)"""

        # Create source site and remote project
        source_site = self._make_site(
            name=REMOTE_SITE_NAME,
            url=REMOTE_SITE_URL,
            mode=SITE_MODE_SOURCE,
            description=REMOTE_SITE_DESC,
            secret=REMOTE_SITE_SECRET,
        )
        self._make_remote_project(
            project_uuid=self.project.sodar_uuid,
            project=self.project,
            site=source_site,
            level=SODAR_CONSTANTS['REMOTE_LEVEL_READ_ROLES'],
        )

        url = reverse(
            'projectroles:api_role_update',
            kwargs={'roleassignment': self.update_as.sodar_uuid},
        )
        patch_data = {'role': PROJECT_ROLE_GUEST}
        response = self.request_knox(url, method='PATCH', data=patch_data)

        # Assert response and role status
        self.assertEqual(response.status_code, 400, msg=response.content)


class TestRoleAssignmentDestroyAPIView(
    RemoteSiteMixin, RemoteProjectMixin, TestCoreAPIViewsBase
):
    """Tests for RoleAssignmentDestroyAPIView"""

    def setUp(self):
        super().setUp()
        self.assign_user = self.make_user('assign_user')

        self.update_as = self._make_assignment(
            self.project, self.assign_user, self.role_contributor
        )

    def test_delete_role(self):
        """Test delete for role assignment deletion"""

        # Assert preconditions
        self.assertEqual(RoleAssignment.objects.count(), 3)

        url = reverse(
            'projectroles:api_role_destroy',
            kwargs={'roleassignment': self.update_as.sodar_uuid},
        )
        response = self.request_knox(url, method='DELETE')

        # Assert response and role status
        self.assertEqual(response.status_code, 204, msg=response.content)
        self.assertEqual(RoleAssignment.objects.count(), 2)
        self.assertEqual(
            RoleAssignment.objects.filter(
                project=self.project, user=self.assign_user
            ).count(),
            0,
        )

    def test_delete_delegate_unauthorized(self):
        """Test delete for delegate deletion without perms (should fail)"""
        new_user = self.make_user('new_user')
        delegate_as = self._make_assignment(
            self.project, new_user, self.role_delegate
        )

        # Assert preconditions
        self.assertEqual(RoleAssignment.objects.count(), 4)

        url = reverse(
            'projectroles:api_role_destroy',
            kwargs={'roleassignment': delegate_as.sodar_uuid},
        )
        # NOTE: Perform record as contributor user
        token = self.get_token(self.assign_user)
        response = self.request_knox(url, method='DELETE', token=token)

        # Assert response and role status
        self.assertEqual(response.status_code, 403, msg=response.content)
        self.assertEqual(RoleAssignment.objects.count(), 4)

    def test_delete_owner(self):
        """Test delete for owner deletion (should fail)"""

        # Assert preconditions
        self.assertEqual(RoleAssignment.objects.count(), 3)

        url = reverse(
            'projectroles:api_role_destroy',
            kwargs={'roleassignment': self.owner_as.sodar_uuid},
        )
        response = self.request_knox(url, method='DELETE')

        # Assert response and project status
        self.assertEqual(response.status_code, 400, msg=response.content)
        self.assertEqual(RoleAssignment.objects.count(), 3)

    @override_settings(PROJECTROLES_SITE_MODE=SITE_MODE_TARGET)
    def test_delete_remote(self):
        """Test delete for a remote project (should fail)"""

        # Create source site and remote project
        source_site = self._make_site(
            name=REMOTE_SITE_NAME,
            url=REMOTE_SITE_URL,
            mode=SITE_MODE_SOURCE,
            description=REMOTE_SITE_DESC,
            secret=REMOTE_SITE_SECRET,
        )
        self._make_remote_project(
            project_uuid=self.project.sodar_uuid,
            project=self.project,
            site=source_site,
            level=SODAR_CONSTANTS['REMOTE_LEVEL_READ_ROLES'],
        )

        # Assert preconditions
        self.assertEqual(RoleAssignment.objects.count(), 3)

        url = reverse(
            'projectroles:api_role_destroy',
            kwargs={'roleassignment': self.update_as.sodar_uuid},
        )
        response = self.request_knox(url, method='DELETE')

        # Assert response and role status
        self.assertEqual(response.status_code, 400, msg=response.content)
        self.assertEqual(RoleAssignment.objects.count(), 3)


class TestRoleAssignmentOwnerTransferAPIView(
    RemoteSiteMixin, RemoteProjectMixin, TestCoreAPIViewsBase
):
    """Tests for RoleAssignmentOwnerTransferAPIView"""

    def setUp(self):
        super().setUp()
        self.assign_user = self.make_user('assign_user')

    def test_transfer_owner(self):
        """Test transferring ownership for a project"""

        # Assign role to new user
        self._make_assignment(
            self.project, self.assign_user, self.role_contributor
        )

        # Assert preconditions
        self.assertEqual(self.project.get_owner().user, self.user)

        url = reverse(
            'projectroles:api_role_owner_transfer',
            kwargs={'project': self.project.sodar_uuid},
        )
        post_data = {
            'new_owner': self.assign_user.username,
            'old_owner_role': self.role_contributor.name,
        }
        response = self.request_knox(url, method='POST', data=post_data)

        # Assert response and project status
        self.assertEqual(response.status_code, 200, msg=response.content)
        self.assertEqual(self.project.get_owner().user, self.assign_user)

    def test_transfer_owner_category(self):
        """Test transferring ownership for a category"""

        # Assign role to new user
        self._make_assignment(
            self.category, self.assign_user, self.role_contributor
        )

        # Assert preconditions
        self.assertEqual(self.category.get_owner().user, self.user)

        url = reverse(
            'projectroles:api_role_owner_transfer',
            kwargs={'project': self.category.sodar_uuid},
        )
        post_data = {
            'new_owner': self.assign_user.username,
            'old_owner_role': self.role_contributor.name,
        }
        response = self.request_knox(url, method='POST', data=post_data)

        # Assert response and project status
        self.assertEqual(response.status_code, 200, msg=response.content)
        self.assertEqual(self.category.get_owner().user, self.assign_user)

    def test_transfer_owner_inherit(self):
        """Test transferring ownership to an inherited owner"""

        # Assign role to new user
        self._make_assignment(
            self.project, self.assign_user, self.role_contributor
        )

        # Set alt owner to current project, make self.user inherited owner
        alt_owner = self.make_user('alt_owner')
        self.owner_as.user = alt_owner
        self.owner_as.save()

        # Assert preconditions
        self.assertEqual(self.project.get_owner().user, alt_owner)
        self.assertEqual(
            self.project.get_owners(inherited_only=True)[0].user, self.user
        )

        url = reverse(
            'projectroles:api_role_owner_transfer',
            kwargs={'project': self.project.sodar_uuid},
        )
        post_data = {
            'new_owner': self.user.username,
            'old_owner_role': self.role_contributor.name,
        }
        response = self.request_knox(url, method='POST', data=post_data)

        # Assert response and project status
        self.assertEqual(response.status_code, 200, msg=response.content)
        self.assertEqual(self.project.get_owner().user, self.user)
        old_owner_as = RoleAssignment.objects.get(
            project=self.project, user=alt_owner
        )
        self.assertEqual(old_owner_as.role, self.role_contributor)

    def test_transfer_owner_no_roles(self):
        """Test transferring ownership to user with no existing roles (should fail)"""

        # NOTE: No role given to user

        url = reverse(
            'projectroles:api_role_owner_transfer',
            kwargs={'project': self.project.sodar_uuid},
        )
        post_data = {
            'new_owner': self.assign_user.username,
            'old_owner_role': self.role_contributor.name,
        }
        response = self.request_knox(url, method='POST', data=post_data)

        # Assert response and project status
        self.assertEqual(response.status_code, 400, msg=response.content)

    @override_settings(PROJECTROLES_SITE_MODE=SITE_MODE_TARGET)
    def test_transfer_remote(self):
        """Test transferring ownership for a remote project (should fail)"""

        # Create source site and remote project
        source_site = self._make_site(
            name=REMOTE_SITE_NAME,
            url=REMOTE_SITE_URL,
            mode=SITE_MODE_SOURCE,
            description=REMOTE_SITE_DESC,
            secret=REMOTE_SITE_SECRET,
        )
        self._make_remote_project(
            project_uuid=self.project.sodar_uuid,
            project=self.project,
            site=source_site,
            level=SODAR_CONSTANTS['REMOTE_LEVEL_READ_ROLES'],
        )

        # Assign role to new user
        self._make_assignment(
            self.project, self.assign_user, self.role_contributor
        )

        # Assert preconditions
        self.assertEqual(self.project.get_owner().user, self.user)

        url = reverse(
            'projectroles:api_role_owner_transfer',
            kwargs={'project': self.project.sodar_uuid},
        )
        post_data = {
            'new_owner': self.assign_user.username,
            'old_owner_role': self.role_contributor.name,
        }
        response = self.request_knox(url, method='POST', data=post_data)

        # Assert response and project status
        self.assertEqual(response.status_code, 400, msg=response.content)
        self.assertEqual(self.project.get_owner().user, self.user)


class TestProjectInviteListAPIView(ProjectInviteMixin, TestCoreAPIViewsBase):
    """Tests for ProjectInviteListAPIView"""

    def setUp(self):
        super().setUp()
        # Create invites
        self.invite = self._make_invite(
            email=INVITE_USER_EMAIL,
            project=self.project,
            role=self.role_guest,
            issuer=self.user,
            message='',
            secret=build_secret(),
        )
        self.invite2 = self._make_invite(
            email=INVITE_USER2_EMAIL,
            project=self.project,
            role=self.role_contributor,
            issuer=self.user,
            message=INVITE_MESSAGE,
            secret=build_secret(),
        )

    def test_get(self):
        """Test ProjectInviteListAPIView get()"""
        url = reverse(
            'projectroles:api_invite_list',
            kwargs={'project': self.project.sodar_uuid},
        )
        response = self.request_knox(url, token=self.get_token(self.user))

        # Assert response
        self.assertEqual(response.status_code, 200)
        response_data = json.loads(response.content)
        self.assertEqual(len(response_data), 2)
        expected = [
            {
                'email': INVITE_USER_EMAIL,
                'project': str(self.project.sodar_uuid),
                'role': self.role_guest.name,
                'issuer': self.get_serialized_user(self.user),
                'date_created': self.get_drf_datetime(self.invite.date_created),
                'date_expire': self.get_drf_datetime(self.invite.date_expire),
                'message': '',
                'sodar_uuid': str(self.invite.sodar_uuid),
            },
            {
                'email': INVITE_USER2_EMAIL,
                'project': str(self.project.sodar_uuid),
                'role': self.role_contributor.name,
                'issuer': self.get_serialized_user(self.user),
                'date_created': self.get_drf_datetime(
                    self.invite2.date_created
                ),
                'date_expire': self.get_drf_datetime(self.invite2.date_expire),
                'message': INVITE_MESSAGE,
                'sodar_uuid': str(self.invite2.sodar_uuid),
            },
        ]
        self.assertEqual(response_data, expected)

    def test_get_inactive(self):
        """Test get() with an inactive invite"""
        self.invite.active = False
        self.invite.save()

        url = reverse(
            'projectroles:api_invite_list',
            kwargs={'project': self.project.sodar_uuid},
        )
        response = self.request_knox(url, token=self.get_token(self.user))

        # Assert response
        self.assertEqual(response.status_code, 200)
        response_data = json.loads(response.content)
        self.assertEqual(len(response_data), 1)
        expected = [
            {
                'email': INVITE_USER2_EMAIL,
                'project': str(self.project.sodar_uuid),
                'role': self.role_contributor.name,
                'issuer': self.get_serialized_user(self.user),
                'date_created': self.get_drf_datetime(
                    self.invite2.date_created
                ),
                'date_expire': self.get_drf_datetime(self.invite2.date_expire),
                'message': INVITE_MESSAGE,
                'sodar_uuid': str(self.invite2.sodar_uuid),
            },
        ]
        self.assertEqual(response_data, expected)


class TestProjectInviteCreateAPIView(
    RemoteSiteMixin, RemoteProjectMixin, TestCoreAPIViewsBase
):
    """Tests for ProjectInviteCreateAPIView"""

    def test_create(self):
        """Test creating a contributor invite for user"""

        # Assert preconditions
        self.assertEqual(
            ProjectInvite.objects.filter(project=self.project).count(), 0
        )

        url = reverse(
            'projectroles:api_invite_create',
            kwargs={'project': self.project.sodar_uuid},
        )
        post_data = {
            'email': INVITE_USER_EMAIL,
            'role': PROJECT_ROLE_CONTRIBUTOR,
            'message': INVITE_MESSAGE,
        }
        response = self.request_knox(url, method='POST', data=post_data)

        # Assert response and role status
        self.assertEqual(response.status_code, 201, msg=response.content)
        self.assertEqual(
            ProjectInvite.objects.filter(project=self.project).count(), 1
        )

        # Assert data
        invite = ProjectInvite.objects.first()
        self.assertEqual(invite.email, INVITE_USER_EMAIL)
        self.assertEqual(invite.role, self.role_contributor)
        self.assertEqual(invite.issuer, self.user)
        self.assertEqual(invite.message, INVITE_MESSAGE)

        # Assert response
        expected = {
            'email': INVITE_USER_EMAIL,
            'project': str(self.project.sodar_uuid),
            'role': PROJECT_ROLE_CONTRIBUTOR,
            'issuer': self.get_serialized_user(self.user),
            'date_created': self.get_drf_datetime(invite.date_created),
            'date_expire': self.get_drf_datetime(invite.date_expire),
            'message': invite.message,
            'sodar_uuid': str(invite.sodar_uuid),
        }
        self.assertEqual(json.loads(response.content), expected)
        self.assertEqual(len(mail.outbox), 1)

    def test_create_owner(self):
        """Test creating an invite for an owner role (should fail)"""

        # Assert preconditions
        self.assertEqual(
            ProjectInvite.objects.filter(project=self.project).count(), 0
        )

        url = reverse(
            'projectroles:api_invite_create',
            kwargs={'project': self.project.sodar_uuid},
        )
        post_data = {
            'email': INVITE_USER_EMAIL,
            'role': PROJECT_ROLE_OWNER,
            'message': INVITE_MESSAGE,
        }
        response = self.request_knox(url, method='POST', data=post_data)

        # Assert response and data
        self.assertEqual(response.status_code, 400, msg=response.content)
        self.assertEqual(
            ProjectInvite.objects.filter(project=self.project).count(), 0
        )
        self.assertEqual(len(mail.outbox), 0)

    def test_create_delegate(self):
        """Test creating an invite for an delegate role"""

        # Assert preconditions
        self.assertEqual(
            ProjectInvite.objects.filter(project=self.project).count(), 0
        )

        url = reverse(
            'projectroles:api_invite_create',
            kwargs={'project': self.project.sodar_uuid},
        )
        post_data = {
            'email': INVITE_USER_EMAIL,
            'role': PROJECT_ROLE_DELEGATE,
            'message': INVITE_MESSAGE,
        }
        response = self.request_knox(url, method='POST', data=post_data)

        # Assert response and data
        self.assertEqual(response.status_code, 201, msg=response.content)
        self.assertEqual(
            ProjectInvite.objects.filter(project=self.project).count(), 1
        )
        invite = ProjectInvite.objects.first()
        self.assertEqual(invite.role, self.role_delegate)
        self.assertEqual(len(mail.outbox), 1)

    @override_settings(PROJECTROLES_DELEGATE_LIMIT=2)
    def test_create_delegate_no_perms(self):
        """Test creating an delegate invite without perms (should fail)"""
        del_user = self.make_user('delegate')
        self._make_assignment(self.project, del_user, self.role_delegate)

        # Assert preconditions
        self.assertEqual(
            ProjectInvite.objects.filter(project=self.project).count(), 0
        )

        url = reverse(
            'projectroles:api_invite_create',
            kwargs={'project': self.project.sodar_uuid},
        )
        post_data = {
            'email': INVITE_USER_EMAIL,
            'role': PROJECT_ROLE_DELEGATE,
            'message': INVITE_MESSAGE,
        }
        response = self.request_knox(
            url, method='POST', data=post_data, token=self.get_token(del_user)
        )

        # Assert response and data
        self.assertEqual(response.status_code, 403, msg=response.content)
        self.assertEqual(
            ProjectInvite.objects.filter(project=self.project).count(), 0
        )
        self.assertEqual(len(mail.outbox), 0)

    def test_create_delegate_limit(self):
        """Test creating an delegate invite with exceeded limit (should fail)"""
        del_user = self.make_user('delegate')
        self._make_assignment(self.project, del_user, self.role_delegate)

        # Assert preconditions
        self.assertEqual(
            ProjectInvite.objects.filter(project=self.project).count(), 0
        )

        url = reverse(
            'projectroles:api_invite_create',
            kwargs={'project': self.project.sodar_uuid},
        )
        post_data = {
            'email': INVITE_USER_EMAIL,
            'role': PROJECT_ROLE_DELEGATE,
            'message': INVITE_MESSAGE,
        }
        response = self.request_knox(url, method='POST', data=post_data)

        # Assert response and data
        self.assertEqual(response.status_code, 400, msg=response.content)
        self.assertEqual(
            ProjectInvite.objects.filter(project=self.project).count(), 0
        )
        self.assertEqual(len(mail.outbox), 0)

    def test_create_invalid_email(self):
        """Test creating an invite with invalid email (should fail)"""

        # Assert preconditions
        self.assertEqual(
            ProjectInvite.objects.filter(project=self.project).count(), 0
        )

        url = reverse(
            'projectroles:api_invite_create',
            kwargs={'project': self.project.sodar_uuid},
        )
        post_data = {
            'email': 'NOT_AN_EMAIL!',
            'role': PROJECT_ROLE_CONTRIBUTOR,
            'message': INVITE_MESSAGE,
        }
        response = self.request_knox(url, method='POST', data=post_data)

        # Assert response and data
        self.assertEqual(response.status_code, 400, msg=response.content)
        self.assertEqual(
            ProjectInvite.objects.filter(project=self.project).count(), 0
        )
        self.assertEqual(len(mail.outbox), 0)

    def test_create_existing_user(self):
        """Test creating an invite for an existing user (should fail)"""
        user = self.make_user('new_user')
        user.email = INVITE_USER_EMAIL
        user.save()

        # Assert preconditions
        self.assertEqual(
            ProjectInvite.objects.filter(project=self.project).count(), 0
        )

        url = reverse(
            'projectroles:api_invite_create',
            kwargs={'project': self.project.sodar_uuid},
        )
        post_data = {
            'email': INVITE_USER_EMAIL,
            'role': PROJECT_ROLE_CONTRIBUTOR,
            'message': INVITE_MESSAGE,
        }
        response = self.request_knox(url, method='POST', data=post_data)

        # Assert response and role status
        self.assertEqual(response.status_code, 400, msg=response.content)
        self.assertEqual(
            ProjectInvite.objects.filter(project=self.project).count(), 0
        )
        self.assertEqual(len(mail.outbox), 0)

    @override_settings(PROJECTROLES_SITE_MODE=SITE_MODE_TARGET)
    def test_create_remote(self):
        """Test creating an invite for a remote project (should fail)"""
        # Set up remote site and project
        source_site = self._make_site(
            name=REMOTE_SITE_NAME,
            url=REMOTE_SITE_URL,
            mode=SITE_MODE_SOURCE,
            description=REMOTE_SITE_DESC,
            secret=REMOTE_SITE_SECRET,
        )
        self._make_remote_project(
            project_uuid=self.project.sodar_uuid,
            project=self.project,
            site=source_site,
            level=SODAR_CONSTANTS['REMOTE_LEVEL_READ_ROLES'],
        )

        # Assert preconditions
        self.assertEqual(
            ProjectInvite.objects.filter(project=self.project).count(), 0
        )

        url = reverse(
            'projectroles:api_invite_create',
            kwargs={'project': self.project.sodar_uuid},
        )
        post_data = {
            'email': INVITE_USER_EMAIL,
            'role': PROJECT_ROLE_CONTRIBUTOR,
            'message': INVITE_MESSAGE,
        }
        response = self.request_knox(url, method='POST', data=post_data)

        # Assert response and role status
        self.assertEqual(response.status_code, 400, msg=response.content)
        self.assertEqual(
            ProjectInvite.objects.filter(project=self.project).count(), 0
        )
        self.assertEqual(len(mail.outbox), 0)


class TestProjectInviteRevokeAPIView(ProjectInviteMixin, TestCoreAPIViewsBase):
    """Tests for ProjectInviteRevokeAPIView"""

    def setUp(self):
        super().setUp()

        # Create invite
        self.invite = self._make_invite(
            email=INVITE_USER_EMAIL,
            project=self.project,
            role=self.role_contributor,
            issuer=self.user,
        )

    def test_revoke(self):
        """Test revoking an invite"""

        # Assert preconditions
        self.assertEqual(self.invite.active, True)

        url = reverse(
            'projectroles:api_invite_revoke',
            kwargs={'projectinvite': self.invite.sodar_uuid},
        )
        response = self.request_knox(url, method='POST')

        # Assert response and invite status
        self.assertEqual(response.status_code, 200, msg=response.content)
        self.invite.refresh_from_db()
        self.assertEqual(self.invite.active, False)

    def test_revoke_inactive(self):
        """Test revoking an already inactive invite (should fail)"""
        self.invite.active = False
        self.invite.save()

        url = reverse(
            'projectroles:api_invite_revoke',
            kwargs={'projectinvite': self.invite.sodar_uuid},
        )
        response = self.request_knox(url, method='POST')

        # Assert response and invite status
        self.assertEqual(response.status_code, 400, msg=response.content)

    def test_revoke_delegate(self):
        """Test revoking a delegate invite with sufficient perms"""
        self.invite.role = self.role_delegate
        self.invite.save()

        url = reverse(
            'projectroles:api_invite_revoke',
            kwargs={'projectinvite': self.invite.sodar_uuid},
        )
        response = self.request_knox(url, method='POST')

        # Assert response and invite status
        self.assertEqual(response.status_code, 200, msg=response.content)
        self.invite.refresh_from_db()
        self.assertEqual(self.invite.active, False)

    def test_revoke_delegate_no_perms(self):
        """Test revoking a delegate invite without perms (should fail)"""
        self.invite.role = self.role_delegate
        self.invite.save()
        delegate = self.make_user('delegate')
        self._make_assignment(self.project, delegate, self.role_delegate)

        url = reverse(
            'projectroles:api_invite_revoke',
            kwargs={'projectinvite': self.invite.sodar_uuid},
        )
        response = self.request_knox(
            url, method='POST', token=self.get_token(delegate)
        )

        # Assert response and invite status
        self.assertEqual(response.status_code, 403, msg=response.content)
        self.invite.refresh_from_db()
        self.assertEqual(self.invite.active, True)

    def test_revoke_not_found(self):
        """Test revoking invite with invalid UUID"""
        url = reverse(
            'projectroles:api_invite_revoke',
            kwargs={'projectinvite': INVALID_UUID},
        )
        response = self.request_knox(url, method='POST')
        self.assertEqual(response.status_code, 404)


class TestProjectInviteResendAPIView(ProjectInviteMixin, TestCoreAPIViewsBase):
    """Tests for ProjectInviteResendAPIView"""

    def setUp(self):
        super().setUp()

        # Create invite
        self.invite = self._make_invite(
            email=INVITE_USER_EMAIL,
            project=self.project,
            role=self.role_contributor,
            issuer=self.user,
        )

    def test_resend(self):
        """Test resending an invite"""

        # Assert preconditions
        self.assertEqual(len(mail.outbox), 0)

        url = reverse(
            'projectroles:api_invite_resend',
            kwargs={'projectinvite': self.invite.sodar_uuid},
        )
        response = self.request_knox(url, method='POST')

        # Assert response and mail status
        self.assertEqual(response.status_code, 200, msg=response.content)
        self.assertEqual(len(mail.outbox), 1)

    def test_resend_inactive(self):
        """Test resending an inactive invite (should fail)"""
        self.invite.active = False
        self.invite.save()

        url = reverse(
            'projectroles:api_invite_resend',
            kwargs={'projectinvite': self.invite.sodar_uuid},
        )
        response = self.request_knox(url, method='POST')

        # Assert response and mail status
        self.assertEqual(response.status_code, 400, msg=response.content)
        self.assertEqual(len(mail.outbox), 0)

    def test_resend_delegate(self):
        """Test resending a delegate invite with sufficient perms"""
        self.invite.role = self.role_delegate
        self.invite.save()

        url = reverse(
            'projectroles:api_invite_resend',
            kwargs={'projectinvite': self.invite.sodar_uuid},
        )
        response = self.request_knox(url, method='POST')

        # Assert response and mail status
        self.assertEqual(response.status_code, 200, msg=response.content)
        self.assertEqual(len(mail.outbox), 1)

    def test_resend_delegate_no_perms(self):
        """Test resending a delegate invite without perms (should fail)"""
        self.invite.role = self.role_delegate
        self.invite.save()
        delegate = self.make_user('delegate')
        self._make_assignment(self.project, delegate, self.role_delegate)

        url = reverse(
            'projectroles:api_invite_resend',
            kwargs={'projectinvite': self.invite.sodar_uuid},
        )
        response = self.request_knox(
            url, method='POST', token=self.get_token(delegate)
        )

        # Assert response and mail status
        self.assertEqual(response.status_code, 403, msg=response.content)
        self.assertEqual(len(mail.outbox), 0)

    def test_resend_not_found(self):
        """Test resending invite with invalid UUID"""
        url = reverse(
            'projectroles:api_invite_resend',
            kwargs={'projectinvite': INVALID_UUID},
        )
        response = self.request_knox(url, method='POST')
        self.assertEqual(response.status_code, 404)


class TestUserListAPIView(TestCoreAPIViewsBase):
    """Tests for UserListAPIView"""

    def setUp(self):
        super().setUp()
        # Create additional users
        self.domain_user = self.make_user('domain_user@domain')

    def test_get(self):
        """Test UserListAPIView get() as a regular user"""
        url = reverse('projectroles:api_user_list')
        response = self.request_knox(
            url, token=self.get_token(self.domain_user)
        )

        # Assert response
        self.assertEqual(response.status_code, 200)
        response_data = json.loads(response.content)
        self.assertEqual(len(response_data), 1)  # System users not returned
        expected = [
            {
                'username': self.domain_user.username,
                'name': self.domain_user.name,
                'email': self.domain_user.email,
                'sodar_uuid': str(self.domain_user.sodar_uuid),
            }
        ]
        self.assertEqual(response_data, expected)

    def test_get_superuser(self):
        """Test UserListAPIView get() as a superuser"""
        url = reverse('projectroles:api_user_list')
        response = self.request_knox(url)  # Default token is for superuser

        # Assert response
        self.assertEqual(response.status_code, 200)
        response_data = json.loads(response.content)
        self.assertEqual(len(response_data), 2)
        expected = [
            {
                'username': self.user.username,
                'name': self.user.name,
                'email': self.user.email,
                'sodar_uuid': str(self.user.sodar_uuid),
            },
            {
                'username': self.domain_user.username,
                'name': self.domain_user.name,
                'email': self.domain_user.email,
                'sodar_uuid': str(self.domain_user.sodar_uuid),
            },
        ]
        self.assertEqual(response_data, expected)


class TestCurrentUserRetrieveAPIView(TestCoreAPIViewsBase):
    """Tests for CurrentUserRetrieveAPIView"""

    def setUp(self):
        super().setUp()
        # Create additional users
        self.domain_user = self.make_user('domain_user@domain')

    def test_get(self):
        """Test CurrentUserRetrieveAPIView get() as a regular user"""
        url = reverse('projectroles:api_user_current')
        response = self.request_knox(
            url, token=self.get_token(self.domain_user)
        )

        # Assert response
        self.assertEqual(response.status_code, 200)
        response_data = json.loads(response.content)
        expected = {
            'username': self.domain_user.username,
            'name': self.domain_user.name,
            'email': self.domain_user.email,
            'sodar_uuid': str(self.domain_user.sodar_uuid),
        }
        self.assertEqual(response_data, expected)

    def test_get_superuser(self):
        """Test CurrentUserRetrieveAPIView get() as superuser"""
        url = reverse('projectroles:api_user_current')
        response = self.request_knox(url)

        # Assert response
        self.assertEqual(response.status_code, 200)
        response_data = json.loads(response.content)
        expected = {
            'username': self.user.username,
            'name': self.user.name,
            'email': self.user.email,
            'sodar_uuid': str(self.user.sodar_uuid),
        }
        self.assertEqual(response_data, expected)


class TestAPIVersioning(TestCoreAPIViewsBase):
    """Tests for REST API view versioning using ProjectRetrieveAPIView"""

    def setUp(self):
        super().setUp()

        self.url = reverse(
            'projectroles:api_project_retrieve',
            kwargs={'project': self.project.sodar_uuid},
        )

    def test_api_versioning(self):
        """Test SODAR API Access with correct version headers"""
        response = self.request_knox(
            self.url,
            media_type=views_api.CORE_API_MEDIA_TYPE,
            version=views_api.CORE_API_DEFAULT_VERSION,
        )
        self.assertEqual(response.status_code, 200)

    def test_api_versioning_invalid_version(self):
        """Test SODAR API Access with unsupported version (should fail)"""
        response = self.request_knox(
            self.url,
            media_type=views_api.CORE_API_MEDIA_TYPE,
            version=CORE_API_VERSION_INVALID,
        )
        self.assertEqual(response.status_code, 406)

    def test_api_versioning_invalid_media_type(self):
        """Test SODAR API Access with unsupported media type (should fail)"""
        response = self.request_knox(
            self.url,
            media_type=CORE_API_MEDIA_TYPE_INVALID,
            version=views_api.CORE_API_MEDIA_TYPE,
        )
        self.assertEqual(response.status_code, 406)


# TODO: To be updated once the legacy API view is redone for SODAR Core v1.0
class TestRemoteProjectGetAPIView(
    ProjectMixin,
    RoleAssignmentMixin,
    RemoteSiteMixin,
    RemoteProjectMixin,
    SODARAPIViewTestMixin,
    TestViewsBase,
):
    """Tests for remote project getting API view"""

    media_type = views_api.CORE_API_MEDIA_TYPE
    api_version = views_api.CORE_API_DEFAULT_VERSION

    def setUp(self):
        super().setUp()

        # Set up projects
        self.category = self._make_project(
            'TestCategory', PROJECT_TYPE_CATEGORY, None
        )
        self.cat_owner_as = self._make_assignment(
            self.category, self.user, self.role_owner
        )
        self.project = self._make_project(
            'TestProject', PROJECT_TYPE_PROJECT, self.category
        )
        self.project_owner_as = self._make_assignment(
            self.project, self.user, self.role_owner
        )

        # Create target site
        self.target_site = self._make_site(
            name=REMOTE_SITE_NAME,
            url=REMOTE_SITE_URL,
            mode=SITE_MODE_TARGET,
            description=REMOTE_SITE_DESC,
            secret=REMOTE_SITE_SECRET,
        )

        # Create remote project
        self.remote_project = self._make_remote_project(
            site=self.target_site,
            project_uuid=self.project.sodar_uuid,
            project=self.project,
            level=SODAR_CONSTANTS['REMOTE_LEVEL_READ_INFO'],
        )

        self.remote_api = RemoteProjectAPI()

    def test_get(self):
        """Test retrieving project data to the target site"""

        response = self.client.get(
            reverse(
                'projectroles:api_remote_get',
                kwargs={'secret': REMOTE_SITE_SECRET},
            )
        )

        self.assertEqual(response.status_code, 200)

        expected = self.remote_api.get_source_data(self.target_site)
        response_dict = json.loads(response.content.decode('utf-8'))

        self.assertEqual(response_dict, expected)

    def test_get_invalid_secret(self):
        """Test retrieving project data with an invalid secret (should fail)"""

        response = self.client.get(
            reverse(
                'projectroles:api_remote_get', kwargs={'secret': build_secret()}
            )
        )

        self.assertEqual(response.status_code, 401)

[((31473, 31528), 'django.test.override_settings', 'override_settings', ([], {'PROJECTROLES_DISABLE_CATEGORIES': '(True)'}), '(PROJECTROLES_DISABLE_CATEGORIES=True)\n', (31490, 31528), False, 'from django.test import override_settings\n'), ((35645, 35703), 'django.test.override_settings', 'override_settings', ([], {'PROJECTROLES_SITE_MODE': 'SITE_MODE_TARGET'}), '(PROJECTROLES_SITE_MODE=SITE_MODE_TARGET)\n', (35662, 35703), False, 'from django.test import override_settings\n'), ((36557, 36615), 'django.test.override_settings', 'override_settings', ([], {'PROJECTROLES_SITE_MODE': 'SITE_MODE_TARGET'}), '(PROJECTROLES_SITE_MODE=SITE_MODE_TARGET)\n', (36574, 36615), False, 'from django.test import override_settings\n'), ((37995, 38091), 'django.test.override_settings', 'override_settings', ([], {'PROJECTROLES_SITE_MODE': 'SITE_MODE_TARGET', 'PROJECTROLES_TARGET_CREATE': '(False)'}), '(PROJECTROLES_SITE_MODE=SITE_MODE_TARGET,\n    PROJECTROLES_TARGET_CREATE=False)\n', (38012, 38091), False, 'from django.test import override_settings\n'), ((54073, 54131), 'django.test.override_settings', 'override_settings', ([], {'PROJECTROLES_SITE_MODE': 'SITE_MODE_TARGET'}), '(PROJECTROLES_SITE_MODE=SITE_MODE_TARGET)\n', (54090, 54131), False, 'from django.test import override_settings\n'), ((63005, 63063), 'django.test.override_settings', 'override_settings', ([], {'PROJECTROLES_SITE_MODE': 'SITE_MODE_TARGET'}), '(PROJECTROLES_SITE_MODE=SITE_MODE_TARGET)\n', (63022, 63063), False, 'from django.test import override_settings\n'), ((70500, 70558), 'django.test.override_settings', 'override_settings', ([], {'PROJECTROLES_SITE_MODE': 'SITE_MODE_TARGET'}), '(PROJECTROLES_SITE_MODE=SITE_MODE_TARGET)\n', (70517, 70558), False, 'from django.test import override_settings\n'), ((74128, 74186), 'django.test.override_settings', 'override_settings', ([], {'PROJECTROLES_SITE_MODE': 'SITE_MODE_TARGET'}), '(PROJECTROLES_SITE_MODE=SITE_MODE_TARGET)\n', (74145, 74186), False, 'from django.test import override_settings\n'), ((79336, 79394), 'django.test.override_settings', 'override_settings', ([], {'PROJECTROLES_SITE_MODE': 'SITE_MODE_TARGET'}), '(PROJECTROLES_SITE_MODE=SITE_MODE_TARGET)\n', (79353, 79394), False, 'from django.test import override_settings\n'), ((87972, 88020), 'django.test.override_settings', 'override_settings', ([], {'PROJECTROLES_DELEGATE_LIMIT': '(2)'}), '(PROJECTROLES_DELEGATE_LIMIT=2)\n', (87989, 88020), False, 'from django.test import override_settings\n'), ((91993, 92051), 'django.test.override_settings', 'override_settings', ([], {'PROJECTROLES_SITE_MODE': 'SITE_MODE_TARGET'}), '(PROJECTROLES_SITE_MODE=SITE_MODE_TARGET)\n', (92010, 92051), False, 'from django.test import override_settings\n'), ((2757, 2792), 'knox.models.AuthToken.objects.create', 'AuthToken.objects.create', ([], {'user': 'user'}), '(user=user)\n', (2781, 2792), False, 'from knox.models import AuthToken\n'), ((5935, 5962), 'projectroles.plugins.get_backend_api', 'get_backend_api', (['"""taskflow"""'], {}), "('taskflow')\n", (5950, 5962), False, 'from projectroles.plugins import change_plugin_status, get_backend_api\n'), ((7783, 7823), 'django.urls.reverse', 'reverse', (['"""projectroles:api_project_list"""'], {}), "('projectroles:api_project_list')\n", (7790, 7823), False, 'from django.urls import reverse\n'), ((7969, 7997), 'json.loads', 'json.loads', (['response.content'], {}), '(response.content)\n', (7979, 7997), False, 'import json\n'), ((10209, 10249), 'django.urls.reverse', 'reverse', (['"""projectroles:api_project_list"""'], {}), "('projectroles:api_project_list')\n", (10216, 10249), False, 'from django.urls import reverse\n'), ((10432, 10460), 'json.loads', 'json.loads', (['response.content'], {}), '(response.content)\n', (10442, 10460), False, 'import json\n'), ((10785, 10825), 'django.urls.reverse', 'reverse', (['"""projectroles:api_project_list"""'], {}), "('projectroles:api_project_list')\n", (10792, 10825), False, 'from django.urls import reverse\n'), ((11008, 11036), 'json.loads', 'json.loads', (['response.content'], {}), '(response.content)\n', (11018, 11036), False, 'import json\n'), ((11315, 11410), 'django.urls.reverse', 'reverse', (['"""projectroles:api_project_retrieve"""'], {'kwargs': "{'project': self.category.sodar_uuid}"}), "('projectroles:api_project_retrieve', kwargs={'project': self.\n    category.sodar_uuid})\n", (11322, 11410), False, 'from django.urls import reverse\n'), ((11586, 11614), 'json.loads', 'json.loads', (['response.content'], {}), '(response.content)\n', (11596, 11614), False, 'import json\n'), ((12651, 12745), 'django.urls.reverse', 'reverse', (['"""projectroles:api_project_retrieve"""'], {'kwargs': "{'project': self.project.sodar_uuid}"}), "('projectroles:api_project_retrieve', kwargs={'project': self.\n    project.sodar_uuid})\n", (12658, 12745), False, 'from django.urls import reverse\n'), ((12921, 12949), 'json.loads', 'json.loads', (['response.content'], {}), '(response.content)\n', (12931, 12949), False, 'import json\n'), ((14006, 14084), 'django.urls.reverse', 'reverse', (['"""projectroles:api_project_retrieve"""'], {'kwargs': "{'project': INVALID_UUID}"}), "('projectroles:api_project_retrieve', kwargs={'project': INVALID_UUID})\n", (14013, 14084), False, 'from django.urls import reverse\n'), ((14627, 14718), 'projectroles.models.RoleAssignment.objects.get', 'RoleAssignment.objects.get', ([], {'project': 'self.project', 'user': 'self.user', 'role': 'self.role_owner'}), '(project=self.project, user=self.user, role=self.\n    role_owner)\n', (14653, 14718), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((14960, 15052), 'projectroles.models.RoleAssignment.objects.get', 'RoleAssignment.objects.get', ([], {'project': 'self.category', 'user': 'self.user', 'role': 'self.role_owner'}), '(project=self.category, user=self.user, role=self\n    .role_owner)\n', (14986, 15052), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((16167, 16261), 'django.urls.reverse', 'reverse', (['"""projectroles:api_project_retrieve"""'], {'kwargs': "{'project': self.project.sodar_uuid}"}), "('projectroles:api_project_retrieve', kwargs={'project': self.\n    project.sodar_uuid})\n", (16174, 16261), False, 'from django.urls import reverse\n'), ((24139, 24181), 'django.urls.reverse', 'reverse', (['"""projectroles:api_project_create"""'], {}), "('projectroles:api_project_create')\n", (24146, 24181), False, 'from django.urls import reverse\n'), ((24790, 24835), 'projectroles.models.Project.objects.get', 'Project.objects.get', ([], {'title': 'NEW_CATEGORY_TITLE'}), '(title=NEW_CATEGORY_TITLE)\n', (24809, 24835), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((24857, 24884), 'django.forms.models.model_to_dict', 'model_to_dict', (['new_category'], {}), '(new_category)\n', (24870, 24884), False, 'from django.forms.models import model_to_dict\n'), ((26340, 26382), 'django.urls.reverse', 'reverse', (['"""projectroles:api_project_create"""'], {}), "('projectroles:api_project_create')\n", (26347, 26382), False, 'from django.urls import reverse\n'), ((27018, 27063), 'projectroles.models.Project.objects.get', 'Project.objects.get', ([], {'title': 'NEW_CATEGORY_TITLE'}), '(title=NEW_CATEGORY_TITLE)\n', (27037, 27063), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((27085, 27112), 'django.forms.models.model_to_dict', 'model_to_dict', (['new_category'], {}), '(new_category)\n', (27098, 27112), False, 'from django.forms.models import model_to_dict\n'), ((28626, 28668), 'django.urls.reverse', 'reverse', (['"""projectroles:api_project_create"""'], {}), "('projectroles:api_project_create')\n", (28633, 28668), False, 'from django.urls import reverse\n'), ((29301, 29345), 'projectroles.models.Project.objects.get', 'Project.objects.get', ([], {'title': 'NEW_PROJECT_TITLE'}), '(title=NEW_PROJECT_TITLE)\n', (29320, 29345), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((29367, 29393), 'django.forms.models.model_to_dict', 'model_to_dict', (['new_project'], {}), '(new_project)\n', (29380, 29393), False, 'from django.forms.models import model_to_dict\n'), ((30894, 30936), 'django.urls.reverse', 'reverse', (['"""projectroles:api_project_create"""'], {}), "('projectroles:api_project_create')\n", (30901, 30936), False, 'from django.urls import reverse\n'), ((31754, 31796), 'django.urls.reverse', 'reverse', (['"""projectroles:api_project_create"""'], {}), "('projectroles:api_project_create')\n", (31761, 31796), False, 'from django.urls import reverse\n'), ((32584, 32626), 'django.urls.reverse', 'reverse', (['"""projectroles:api_project_create"""'], {}), "('projectroles:api_project_create')\n", (32591, 32626), False, 'from django.urls import reverse\n'), ((33409, 33451), 'django.urls.reverse', 'reverse', (['"""projectroles:api_project_create"""'], {}), "('projectroles:api_project_create')\n", (33416, 33451), False, 'from django.urls import reverse\n'), ((34233, 34275), 'django.urls.reverse', 'reverse', (['"""projectroles:api_project_create"""'], {}), "('projectroles:api_project_create')\n", (34240, 34275), False, 'from django.urls import reverse\n'), ((35042, 35084), 'django.urls.reverse', 'reverse', (['"""projectroles:api_project_create"""'], {}), "('projectroles:api_project_create')\n", (35049, 35084), False, 'from django.urls import reverse\n'), ((35931, 35973), 'django.urls.reverse', 'reverse', (['"""projectroles:api_project_create"""'], {}), "('projectroles:api_project_create')\n", (35938, 35973), False, 'from django.urls import reverse\n'), ((37369, 37411), 'django.urls.reverse', 'reverse', (['"""projectroles:api_project_create"""'], {}), "('projectroles:api_project_create')\n", (37376, 37411), False, 'from django.urls import reverse\n'), ((38356, 38398), 'django.urls.reverse', 'reverse', (['"""projectroles:api_project_create"""'], {}), "('projectroles:api_project_create')\n", (38363, 38398), False, 'from django.urls import reverse\n'), ((39297, 39390), 'django.urls.reverse', 'reverse', (['"""projectroles:api_project_update"""'], {'kwargs': "{'project': self.category.sodar_uuid}"}), "('projectroles:api_project_update', kwargs={'project': self.category\n    .sodar_uuid})\n", (39304, 39390), False, 'from django.urls import reverse\n'), ((40063, 40091), 'django.forms.models.model_to_dict', 'model_to_dict', (['self.category'], {}), '(self.category)\n', (40076, 40091), False, 'from django.forms.models import model_to_dict\n'), ((41611, 41703), 'django.urls.reverse', 'reverse', (['"""projectroles:api_project_update"""'], {'kwargs': "{'project': self.project.sodar_uuid}"}), "('projectroles:api_project_update', kwargs={'project': self.project.\n    sodar_uuid})\n", (41618, 41703), False, 'from django.urls import reverse\n'), ((42401, 42428), 'django.forms.models.model_to_dict', 'model_to_dict', (['self.project'], {}), '(self.project)\n', (42414, 42428), False, 'from django.forms.models import model_to_dict\n'), ((44023, 44116), 'django.urls.reverse', 'reverse', (['"""projectroles:api_project_update"""'], {'kwargs': "{'project': self.category.sodar_uuid}"}), "('projectroles:api_project_update', kwargs={'project': self.category\n    .sodar_uuid})\n", (44030, 44116), False, 'from django.urls import reverse\n'), ((44637, 44665), 'django.forms.models.model_to_dict', 'model_to_dict', (['self.category'], {}), '(self.category)\n', (44650, 44665), False, 'from django.forms.models import model_to_dict\n'), ((46302, 46394), 'django.urls.reverse', 'reverse', (['"""projectroles:api_project_update"""'], {'kwargs': "{'project': self.project.sodar_uuid}"}), "('projectroles:api_project_update', kwargs={'project': self.project.\n    sodar_uuid})\n", (46309, 46394), False, 'from django.urls import reverse\n'), ((46955, 46982), 'django.forms.models.model_to_dict', 'model_to_dict', (['self.project'], {}), '(self.project)\n', (46968, 46982), False, 'from django.forms.models import model_to_dict\n'), ((48656, 48748), 'django.urls.reverse', 'reverse', (['"""projectroles:api_project_update"""'], {'kwargs': "{'project': self.project.sodar_uuid}"}), "('projectroles:api_project_update', kwargs={'project': self.project.\n    sodar_uuid})\n", (48663, 48748), False, 'from django.urls import reverse\n'), ((49488, 49580), 'django.urls.reverse', 'reverse', (['"""projectroles:api_project_update"""'], {'kwargs': "{'project': self.project.sodar_uuid}"}), "('projectroles:api_project_update', kwargs={'project': self.project.\n    sodar_uuid})\n", (49495, 49580), False, 'from django.urls import reverse\n'), ((49942, 49969), 'django.forms.models.model_to_dict', 'model_to_dict', (['self.project'], {}), '(self.project)\n', (49955, 49969), False, 'from django.forms.models import model_to_dict\n'), ((50840, 50932), 'django.urls.reverse', 'reverse', (['"""projectroles:api_project_update"""'], {'kwargs': "{'project': self.project.sodar_uuid}"}), "('projectroles:api_project_update', kwargs={'project': self.project.\n    sodar_uuid})\n", (50847, 50932), False, 'from django.urls import reverse\n'), ((51703, 51795), 'django.urls.reverse', 'reverse', (['"""projectroles:api_project_update"""'], {'kwargs': "{'project': self.project.sodar_uuid}"}), "('projectroles:api_project_update', kwargs={'project': self.project.\n    sodar_uuid})\n", (51710, 51795), False, 'from django.urls import reverse\n'), ((52424, 52516), 'django.urls.reverse', 'reverse', (['"""projectroles:api_project_update"""'], {'kwargs': "{'project': self.project.sodar_uuid}"}), "('projectroles:api_project_update', kwargs={'project': self.project.\n    sodar_uuid})\n", (52431, 52516), False, 'from django.urls import reverse\n'), ((53223, 53316), 'django.urls.reverse', 'reverse', (['"""projectroles:api_project_update"""'], {'kwargs': "{'project': self.category.sodar_uuid}"}), "('projectroles:api_project_update', kwargs={'project': self.category\n    .sodar_uuid})\n", (53230, 53316), False, 'from django.urls import reverse\n'), ((53715, 53807), 'django.urls.reverse', 'reverse', (['"""projectroles:api_project_update"""'], {'kwargs': "{'project': self.project.sodar_uuid}"}), "('projectroles:api_project_update', kwargs={'project': self.project.\n    sodar_uuid})\n", (53722, 53807), False, 'from django.urls import reverse\n'), ((54769, 54861), 'django.urls.reverse', 'reverse', (['"""projectroles:api_project_update"""'], {'kwargs': "{'project': self.project.sodar_uuid}"}), "('projectroles:api_project_update', kwargs={'project': self.project.\n    sodar_uuid})\n", (54776, 54861), False, 'from django.urls import reverse\n'), ((55746, 55835), 'django.urls.reverse', 'reverse', (['"""projectroles:api_role_create"""'], {'kwargs': "{'project': self.project.sodar_uuid}"}), "('projectroles:api_role_create', kwargs={'project': self.project.\n    sodar_uuid})\n", (55753, 55835), False, 'from django.urls import reverse\n'), ((56974, 57063), 'django.urls.reverse', 'reverse', (['"""projectroles:api_role_create"""'], {'kwargs': "{'project': self.project.sodar_uuid}"}), "('projectroles:api_role_create', kwargs={'project': self.project.\n    sodar_uuid})\n", (56981, 57063), False, 'from django.urls import reverse\n'), ((57766, 57855), 'django.urls.reverse', 'reverse', (['"""projectroles:api_role_create"""'], {'kwargs': "{'project': self.project.sodar_uuid}"}), "('projectroles:api_role_create', kwargs={'project': self.project.\n    sodar_uuid})\n", (57773, 57855), False, 'from django.urls import reverse\n'), ((58899, 58988), 'django.urls.reverse', 'reverse', (['"""projectroles:api_role_create"""'], {'kwargs': "{'project': self.project.sodar_uuid}"}), "('projectroles:api_role_create', kwargs={'project': self.project.\n    sodar_uuid})\n", (58906, 58988), False, 'from django.urls import reverse\n'), ((59672, 59761), 'django.urls.reverse', 'reverse', (['"""projectroles:api_role_create"""'], {'kwargs': "{'project': self.project.sodar_uuid}"}), "('projectroles:api_role_create', kwargs={'project': self.project.\n    sodar_uuid})\n", (59679, 59761), False, 'from django.urls import reverse\n'), ((60391, 60480), 'django.urls.reverse', 'reverse', (['"""projectroles:api_role_create"""'], {'kwargs': "{'project': self.project.sodar_uuid}"}), "('projectroles:api_role_create', kwargs={'project': self.project.\n    sodar_uuid})\n", (60398, 60480), False, 'from django.urls import reverse\n'), ((61303, 61393), 'django.urls.reverse', 'reverse', (['"""projectroles:api_role_create"""'], {'kwargs': "{'project': self.category.sodar_uuid}"}), "('projectroles:api_role_create', kwargs={'project': self.category.\n    sodar_uuid})\n", (61310, 61393), False, 'from django.urls import reverse\n'), ((61994, 62083), 'django.urls.reverse', 'reverse', (['"""projectroles:api_role_create"""'], {'kwargs': "{'project': self.project.sodar_uuid}"}), "('projectroles:api_role_create', kwargs={'project': self.project.\n    sodar_uuid})\n", (62001, 62083), False, 'from django.urls import reverse\n'), ((63829, 63918), 'django.urls.reverse', 'reverse', (['"""projectroles:api_role_create"""'], {'kwargs': "{'project': self.project.sodar_uuid}"}), "('projectroles:api_role_create', kwargs={'project': self.project.\n    sodar_uuid})\n", (63836, 63918), False, 'from django.urls import reverse\n'), ((64954, 65052), 'django.urls.reverse', 'reverse', (['"""projectroles:api_role_update"""'], {'kwargs': "{'roleassignment': self.update_as.sodar_uuid}"}), "('projectroles:api_role_update', kwargs={'roleassignment': self.\n    update_as.sodar_uuid})\n", (64961, 65052), False, 'from django.urls import reverse\n'), ((65551, 65580), 'django.forms.models.model_to_dict', 'model_to_dict', (['self.update_as'], {}), '(self.update_as)\n', (65564, 65580), False, 'from django.forms.models import model_to_dict\n'), ((66304, 66402), 'django.urls.reverse', 'reverse', (['"""projectroles:api_role_update"""'], {'kwargs': "{'roleassignment': self.update_as.sodar_uuid}"}), "('projectroles:api_role_update', kwargs={'roleassignment': self.\n    update_as.sodar_uuid})\n", (66311, 66402), False, 'from django.urls import reverse\n'), ((66828, 66857), 'django.forms.models.model_to_dict', 'model_to_dict', (['self.update_as'], {}), '(self.update_as)\n', (66841, 66857), False, 'from django.forms.models import model_to_dict\n'), ((67595, 67693), 'django.urls.reverse', 'reverse', (['"""projectroles:api_role_update"""'], {'kwargs': "{'roleassignment': self.update_as.sodar_uuid}"}), "('projectroles:api_role_update', kwargs={'roleassignment': self.\n    update_as.sodar_uuid})\n", (67602, 67693), False, 'from django.urls import reverse\n'), ((68180, 68278), 'django.urls.reverse', 'reverse', (['"""projectroles:api_role_update"""'], {'kwargs': "{'roleassignment': self.update_as.sodar_uuid}"}), "('projectroles:api_role_update', kwargs={'roleassignment': self.\n    update_as.sodar_uuid})\n", (68187, 68278), False, 'from django.urls import reverse\n'), ((68793, 68891), 'django.urls.reverse', 'reverse', (['"""projectroles:api_role_update"""'], {'kwargs': "{'roleassignment': self.update_as.sodar_uuid}"}), "('projectroles:api_role_update', kwargs={'roleassignment': self.\n    update_as.sodar_uuid})\n", (68800, 68891), False, 'from django.urls import reverse\n'), ((69319, 69348), 'django.forms.models.model_to_dict', 'model_to_dict', (['self.update_as'], {}), '(self.update_as)\n', (69332, 69348), False, 'from django.forms.models import model_to_dict\n'), ((70133, 70231), 'django.urls.reverse', 'reverse', (['"""projectroles:api_role_update"""'], {'kwargs': "{'roleassignment': self.update_as.sodar_uuid}"}), "('projectroles:api_role_update', kwargs={'roleassignment': self.\n    update_as.sodar_uuid})\n", (70140, 70231), False, 'from django.urls import reverse\n'), ((71196, 71294), 'django.urls.reverse', 'reverse', (['"""projectroles:api_role_update"""'], {'kwargs': "{'roleassignment': self.update_as.sodar_uuid}"}), "('projectroles:api_role_update', kwargs={'roleassignment': self.\n    update_as.sodar_uuid})\n", (71203, 71294), False, 'from django.urls import reverse\n'), ((72146, 72245), 'django.urls.reverse', 'reverse', (['"""projectroles:api_role_destroy"""'], {'kwargs': "{'roleassignment': self.update_as.sodar_uuid}"}), "('projectroles:api_role_destroy', kwargs={'roleassignment': self.\n    update_as.sodar_uuid})\n", (72153, 72245), False, 'from django.urls import reverse\n'), ((73078, 73173), 'django.urls.reverse', 'reverse', (['"""projectroles:api_role_destroy"""'], {'kwargs': "{'roleassignment': delegate_as.sodar_uuid}"}), "('projectroles:api_role_destroy', kwargs={'roleassignment':\n    delegate_as.sodar_uuid})\n", (73085, 73173), False, 'from django.urls import reverse\n'), ((73754, 73852), 'django.urls.reverse', 'reverse', (['"""projectroles:api_role_destroy"""'], {'kwargs': "{'roleassignment': self.owner_as.sodar_uuid}"}), "('projectroles:api_role_destroy', kwargs={'roleassignment': self.\n    owner_as.sodar_uuid})\n", (73761, 73852), False, 'from django.urls import reverse\n'), ((74892, 74991), 'django.urls.reverse', 'reverse', (['"""projectroles:api_role_destroy"""'], {'kwargs': "{'roleassignment': self.update_as.sodar_uuid}"}), "('projectroles:api_role_destroy', kwargs={'roleassignment': self.\n    update_as.sodar_uuid})\n", (74899, 74991), False, 'from django.urls import reverse\n'), ((75877, 75974), 'django.urls.reverse', 'reverse', (['"""projectroles:api_role_owner_transfer"""'], {'kwargs': "{'project': self.project.sodar_uuid}"}), "('projectroles:api_role_owner_transfer', kwargs={'project': self.\n    project.sodar_uuid})\n", (75884, 75974), False, 'from django.urls import reverse\n'), ((76774, 76872), 'django.urls.reverse', 'reverse', (['"""projectroles:api_role_owner_transfer"""'], {'kwargs': "{'project': self.category.sodar_uuid}"}), "('projectroles:api_role_owner_transfer', kwargs={'project': self.\n    category.sodar_uuid})\n", (76781, 76872), False, 'from django.urls import reverse\n'), ((77981, 78078), 'django.urls.reverse', 'reverse', (['"""projectroles:api_role_owner_transfer"""'], {'kwargs': "{'project': self.project.sodar_uuid}"}), "('projectroles:api_role_owner_transfer', kwargs={'project': self.\n    project.sodar_uuid})\n", (77988, 78078), False, 'from django.urls import reverse\n'), ((78527, 78591), 'projectroles.models.RoleAssignment.objects.get', 'RoleAssignment.objects.get', ([], {'project': 'self.project', 'user': 'alt_owner'}), '(project=self.project, user=alt_owner)\n', (78553, 78591), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((78867, 78964), 'django.urls.reverse', 'reverse', (['"""projectroles:api_role_owner_transfer"""'], {'kwargs': "{'project': self.project.sodar_uuid}"}), "('projectroles:api_role_owner_transfer', kwargs={'project': self.\n    project.sodar_uuid})\n", (78874, 78964), False, 'from django.urls import reverse\n'), ((80267, 80364), 'django.urls.reverse', 'reverse', (['"""projectroles:api_role_owner_transfer"""'], {'kwargs': "{'project': self.project.sodar_uuid}"}), "('projectroles:api_role_owner_transfer', kwargs={'project': self.\n    project.sodar_uuid})\n", (80274, 80364), False, 'from django.urls import reverse\n'), ((81592, 81681), 'django.urls.reverse', 'reverse', (['"""projectroles:api_invite_list"""'], {'kwargs': "{'project': self.project.sodar_uuid}"}), "('projectroles:api_invite_list', kwargs={'project': self.project.\n    sodar_uuid})\n", (81599, 81681), False, 'from django.urls import reverse\n'), ((81890, 81918), 'json.loads', 'json.loads', (['response.content'], {}), '(response.content)\n', (81900, 81918), False, 'import json\n'), ((83246, 83335), 'django.urls.reverse', 'reverse', (['"""projectroles:api_invite_list"""'], {'kwargs': "{'project': self.project.sodar_uuid}"}), "('projectroles:api_invite_list', kwargs={'project': self.project.\n    sodar_uuid})\n", (83253, 83335), False, 'from django.urls import reverse\n'), ((83544, 83572), 'json.loads', 'json.loads', (['response.content'], {}), '(response.content)\n', (83554, 83572), False, 'import json\n'), ((84646, 84737), 'django.urls.reverse', 'reverse', (['"""projectroles:api_invite_create"""'], {'kwargs': "{'project': self.project.sodar_uuid}"}), "('projectroles:api_invite_create', kwargs={'project': self.project.\n    sodar_uuid})\n", (84653, 84737), False, 'from django.urls import reverse\n'), ((85265, 85294), 'projectroles.models.ProjectInvite.objects.first', 'ProjectInvite.objects.first', ([], {}), '()\n', (85292, 85294), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((86381, 86472), 'django.urls.reverse', 'reverse', (['"""projectroles:api_invite_create"""'], {'kwargs': "{'project': self.project.sodar_uuid}"}), "('projectroles:api_invite_create', kwargs={'project': self.project.\n    sodar_uuid})\n", (86388, 86472), False, 'from django.urls import reverse\n'), ((87246, 87337), 'django.urls.reverse', 'reverse', (['"""projectroles:api_invite_create"""'], {'kwargs': "{'project': self.project.sodar_uuid}"}), "('projectroles:api_invite_create', kwargs={'project': self.project.\n    sodar_uuid})\n", (87253, 87337), False, 'from django.urls import reverse\n'), ((87832, 87861), 'projectroles.models.ProjectInvite.objects.first', 'ProjectInvite.objects.first', ([], {}), '()\n', (87859, 87861), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((88418, 88509), 'django.urls.reverse', 'reverse', (['"""projectroles:api_invite_create"""'], {'kwargs': "{'project': self.project.sodar_uuid}"}), "('projectroles:api_invite_create', kwargs={'project': self.project.\n    sodar_uuid})\n", (88425, 88509), False, 'from django.urls import reverse\n'), ((89488, 89579), 'django.urls.reverse', 'reverse', (['"""projectroles:api_invite_create"""'], {'kwargs': "{'project': self.project.sodar_uuid}"}), "('projectroles:api_invite_create', kwargs={'project': self.project.\n    sodar_uuid})\n", (89495, 89579), False, 'from django.urls import reverse\n'), ((90373, 90464), 'django.urls.reverse', 'reverse', (['"""projectroles:api_invite_create"""'], {'kwargs': "{'project': self.project.sodar_uuid}"}), "('projectroles:api_invite_create', kwargs={'project': self.project.\n    sodar_uuid})\n", (90380, 90464), False, 'from django.urls import reverse\n'), ((91362, 91453), 'django.urls.reverse', 'reverse', (['"""projectroles:api_invite_create"""'], {'kwargs': "{'project': self.project.sodar_uuid}"}), "('projectroles:api_invite_create', kwargs={'project': self.project.\n    sodar_uuid})\n", (91369, 91453), False, 'from django.urls import reverse\n'), ((92811, 92902), 'django.urls.reverse', 'reverse', (['"""projectroles:api_invite_create"""'], {'kwargs': "{'project': self.project.sodar_uuid}"}), "('projectroles:api_invite_create', kwargs={'project': self.project.\n    sodar_uuid})\n", (92818, 92902), False, 'from django.urls import reverse\n'), ((93992, 94088), 'django.urls.reverse', 'reverse', (['"""projectroles:api_invite_revoke"""'], {'kwargs': "{'projectinvite': self.invite.sodar_uuid}"}), "('projectroles:api_invite_revoke', kwargs={'projectinvite': self.\n    invite.sodar_uuid})\n", (93999, 94088), False, 'from django.urls import reverse\n'), ((94568, 94664), 'django.urls.reverse', 'reverse', (['"""projectroles:api_invite_revoke"""'], {'kwargs': "{'projectinvite': self.invite.sodar_uuid}"}), "('projectroles:api_invite_revoke', kwargs={'projectinvite': self.\n    invite.sodar_uuid})\n", (94575, 94664), False, 'from django.urls import reverse\n'), ((95064, 95160), 'django.urls.reverse', 'reverse', (['"""projectroles:api_invite_revoke"""'], {'kwargs': "{'projectinvite': self.invite.sodar_uuid}"}), "('projectroles:api_invite_revoke', kwargs={'projectinvite': self.\n    invite.sodar_uuid})\n", (95071, 95160), False, 'from django.urls import reverse\n'), ((95785, 95881), 'django.urls.reverse', 'reverse', (['"""projectroles:api_invite_revoke"""'], {'kwargs': "{'projectinvite': self.invite.sodar_uuid}"}), "('projectroles:api_invite_revoke', kwargs={'projectinvite': self.\n    invite.sodar_uuid})\n", (95792, 95881), False, 'from django.urls import reverse\n'), ((96336, 96421), 'django.urls.reverse', 'reverse', (['"""projectroles:api_invite_revoke"""'], {'kwargs': "{'projectinvite': INVALID_UUID}"}), "('projectroles:api_invite_revoke', kwargs={'projectinvite':\n    INVALID_UUID})\n", (96343, 96421), False, 'from django.urls import reverse\n'), ((97114, 97210), 'django.urls.reverse', 'reverse', (['"""projectroles:api_invite_resend"""'], {'kwargs': "{'projectinvite': self.invite.sodar_uuid}"}), "('projectroles:api_invite_resend', kwargs={'projectinvite': self.\n    invite.sodar_uuid})\n", (97121, 97210), False, 'from django.urls import reverse\n'), ((97637, 97733), 'django.urls.reverse', 'reverse', (['"""projectroles:api_invite_resend"""'], {'kwargs': "{'projectinvite': self.invite.sodar_uuid}"}), "('projectroles:api_invite_resend', kwargs={'projectinvite': self.\n    invite.sodar_uuid})\n", (97644, 97733), False, 'from django.urls import reverse\n'), ((98178, 98274), 'django.urls.reverse', 'reverse', (['"""projectroles:api_invite_resend"""'], {'kwargs': "{'projectinvite': self.invite.sodar_uuid}"}), "('projectroles:api_invite_resend', kwargs={'projectinvite': self.\n    invite.sodar_uuid})\n", (98185, 98274), False, 'from django.urls import reverse\n'), ((98854, 98950), 'django.urls.reverse', 'reverse', (['"""projectroles:api_invite_resend"""'], {'kwargs': "{'projectinvite': self.invite.sodar_uuid}"}), "('projectroles:api_invite_resend', kwargs={'projectinvite': self.\n    invite.sodar_uuid})\n", (98861, 98950), False, 'from django.urls import reverse\n'), ((99361, 99446), 'django.urls.reverse', 'reverse', (['"""projectroles:api_invite_resend"""'], {'kwargs': "{'projectinvite': INVALID_UUID}"}), "('projectroles:api_invite_resend', kwargs={'projectinvite':\n    INVALID_UUID})\n", (99368, 99446), False, 'from django.urls import reverse\n'), ((99916, 99953), 'django.urls.reverse', 'reverse', (['"""projectroles:api_user_list"""'], {}), "('projectroles:api_user_list')\n", (99923, 99953), False, 'from django.urls import reverse\n'), ((100161, 100189), 'json.loads', 'json.loads', (['response.content'], {}), '(response.content)\n', (100171, 100189), False, 'import json\n'), ((100696, 100733), 'django.urls.reverse', 'reverse', (['"""projectroles:api_user_list"""'], {}), "('projectroles:api_user_list')\n", (100703, 100733), False, 'from django.urls import reverse\n'), ((100913, 100941), 'json.loads', 'json.loads', (['response.content'], {}), '(response.content)\n', (100923, 100941), False, 'import json\n'), ((101893, 101933), 'django.urls.reverse', 'reverse', (['"""projectroles:api_user_current"""'], {}), "('projectroles:api_user_current')\n", (101900, 101933), False, 'from django.urls import reverse\n'), ((102141, 102169), 'json.loads', 'json.loads', (['response.content'], {}), '(response.content)\n', (102151, 102169), False, 'import json\n'), ((102564, 102604), 'django.urls.reverse', 'reverse', (['"""projectroles:api_user_current"""'], {}), "('projectroles:api_user_current')\n", (102571, 102604), False, 'from django.urls import reverse\n'), ((102750, 102778), 'json.loads', 'json.loads', (['response.content'], {}), '(response.content)\n', (102760, 102778), False, 'import json\n'), ((103220, 103314), 'django.urls.reverse', 'reverse', (['"""projectroles:api_project_retrieve"""'], {'kwargs': "{'project': self.project.sodar_uuid}"}), "('projectroles:api_project_retrieve', kwargs={'project': self.\n    project.sodar_uuid})\n", (103227, 103314), False, 'from django.urls import reverse\n'), ((105896, 105914), 'projectroles.remote_projects.RemoteProjectAPI', 'RemoteProjectAPI', ([], {}), '()\n', (105912, 105914), False, 'from projectroles.remote_projects import RemoteProjectAPI\n'), ((5976, 6046), 'projectroles.plugins.change_plugin_status', 'change_plugin_status', ([], {'name': '"""taskflow"""', 'status': '(1)', 'plugin_type': '"""backend"""'}), "(name='taskflow', status=1, plugin_type='backend')\n", (5996, 6046), False, 'from projectroles.plugins import change_plugin_status, get_backend_api\n'), ((6141, 6192), 'projectroles.models.Role.objects.get_or_create', 'Role.objects.get_or_create', ([], {'name': 'PROJECT_ROLE_OWNER'}), '(name=PROJECT_ROLE_OWNER)\n', (6167, 6192), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((6225, 6279), 'projectroles.models.Role.objects.get_or_create', 'Role.objects.get_or_create', ([], {'name': 'PROJECT_ROLE_DELEGATE'}), '(name=PROJECT_ROLE_DELEGATE)\n', (6251, 6279), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((6337, 6394), 'projectroles.models.Role.objects.get_or_create', 'Role.objects.get_or_create', ([], {'name': 'PROJECT_ROLE_CONTRIBUTOR'}), '(name=PROJECT_ROLE_CONTRIBUTOR)\n', (6363, 6394), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((6446, 6497), 'projectroles.models.Role.objects.get_or_create', 'Role.objects.get_or_create', ([], {'name': 'PROJECT_ROLE_GUEST'}), '(name=PROJECT_ROLE_GUEST)\n', (6472, 6497), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((17869, 17897), 'json.loads', 'json.loads', (['response.content'], {}), '(response.content)\n', (17879, 17897), False, 'import json\n'), ((24096, 24119), 'projectroles.models.Project.objects.count', 'Project.objects.count', ([], {}), '()\n', (24117, 24119), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((24706, 24729), 'projectroles.models.Project.objects.count', 'Project.objects.count', ([], {}), '()\n', (24727, 24729), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((26090, 26118), 'json.loads', 'json.loads', (['response.content'], {}), '(response.content)\n', (26100, 26118), False, 'import json\n'), ((26297, 26320), 'projectroles.models.Project.objects.count', 'Project.objects.count', ([], {}), '()\n', (26318, 26320), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((26934, 26957), 'projectroles.models.Project.objects.count', 'Project.objects.count', ([], {}), '()\n', (26955, 26957), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((28385, 28413), 'json.loads', 'json.loads', (['response.content'], {}), '(response.content)\n', (28395, 28413), False, 'import json\n'), ((28583, 28606), 'projectroles.models.Project.objects.count', 'Project.objects.count', ([], {}), '()\n', (28604, 28606), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((29218, 29241), 'projectroles.models.Project.objects.count', 'Project.objects.count', ([], {}), '()\n', (29239, 29241), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((30653, 30681), 'json.loads', 'json.loads', (['response.content'], {}), '(response.content)\n', (30663, 30681), False, 'import json\n'), ((30851, 30874), 'projectroles.models.Project.objects.count', 'Project.objects.count', ([], {}), '()\n', (30872, 30874), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((31439, 31462), 'projectroles.models.Project.objects.count', 'Project.objects.count', ([], {}), '()\n', (31460, 31462), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((31711, 31734), 'projectroles.models.Project.objects.count', 'Project.objects.count', ([], {}), '()\n', (31732, 31734), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((32319, 32342), 'projectroles.models.Project.objects.count', 'Project.objects.count', ([], {}), '()\n', (32340, 32342), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((32541, 32564), 'projectroles.models.Project.objects.count', 'Project.objects.count', ([], {}), '()\n', (32562, 32564), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((33155, 33178), 'projectroles.models.Project.objects.count', 'Project.objects.count', ([], {}), '()\n', (33176, 33178), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((33366, 33389), 'projectroles.models.Project.objects.count', 'Project.objects.count', ([], {}), '()\n', (33387, 33389), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((33966, 33989), 'projectroles.models.Project.objects.count', 'Project.objects.count', ([], {}), '()\n', (33987, 33989), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((34190, 34213), 'projectroles.models.Project.objects.count', 'Project.objects.count', ([], {}), '()\n', (34211, 34213), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((34786, 34809), 'projectroles.models.Project.objects.count', 'Project.objects.count', ([], {}), '()\n', (34807, 34809), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((34999, 35022), 'projectroles.models.Project.objects.count', 'Project.objects.count', ([], {}), '()\n', (35020, 35022), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((35611, 35634), 'projectroles.models.Project.objects.count', 'Project.objects.count', ([], {}), '()\n', (35632, 35634), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((35888, 35911), 'projectroles.models.Project.objects.count', 'Project.objects.count', ([], {}), '()\n', (35909, 35911), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((36523, 36546), 'projectroles.models.Project.objects.count', 'Project.objects.count', ([], {}), '()\n', (36544, 36546), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((37326, 37349), 'projectroles.models.Project.objects.count', 'Project.objects.count', ([], {}), '()\n', (37347, 37349), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((37961, 37984), 'projectroles.models.Project.objects.count', 'Project.objects.count', ([], {}), '()\n', (37982, 37984), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((38313, 38336), 'projectroles.models.Project.objects.count', 'Project.objects.count', ([], {}), '()\n', (38334, 38336), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((38948, 38971), 'projectroles.models.Project.objects.count', 'Project.objects.count', ([], {}), '()\n', (38969, 38971), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((39254, 39277), 'projectroles.models.Project.objects.count', 'Project.objects.count', ([], {}), '()\n', (39275, 39277), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((39941, 39964), 'projectroles.models.Project.objects.count', 'Project.objects.count', ([], {}), '()\n', (39962, 39964), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((41392, 41420), 'json.loads', 'json.loads', (['response.content'], {}), '(response.content)\n', (41402, 41420), False, 'import json\n'), ((41568, 41591), 'projectroles.models.Project.objects.count', 'Project.objects.count', ([], {}), '()\n', (41589, 41591), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((42280, 42303), 'projectroles.models.Project.objects.count', 'Project.objects.count', ([], {}), '()\n', (42301, 42303), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((43789, 43817), 'json.loads', 'json.loads', (['response.content'], {}), '(response.content)\n', (43799, 43817), False, 'import json\n'), ((43980, 44003), 'projectroles.models.Project.objects.count', 'Project.objects.count', ([], {}), '()\n', (44001, 44003), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((44515, 44538), 'projectroles.models.Project.objects.count', 'Project.objects.count', ([], {}), '()\n', (44536, 44538), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((46070, 46098), 'json.loads', 'json.loads', (['response.content'], {}), '(response.content)\n', (46080, 46098), False, 'import json\n'), ((46259, 46282), 'projectroles.models.Project.objects.count', 'Project.objects.count', ([], {}), '()\n', (46280, 46282), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((46834, 46857), 'projectroles.models.Project.objects.count', 'Project.objects.count', ([], {}), '()\n', (46855, 46857), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((48444, 48472), 'json.loads', 'json.loads', (['response.content'], {}), '(response.content)\n', (48454, 48472), False, 'import json\n'), ((56829, 56857), 'json.loads', 'json.loads', (['response.content'], {}), '(response.content)\n', (56839, 56857), False, 'import json\n'), ((64904, 64934), 'projectroles.models.RoleAssignment.objects.count', 'RoleAssignment.objects.count', ([], {}), '()\n', (64932, 64934), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((65421, 65451), 'projectroles.models.RoleAssignment.objects.count', 'RoleAssignment.objects.count', ([], {}), '()\n', (65449, 65451), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((66162, 66190), 'json.loads', 'json.loads', (['response.content'], {}), '(response.content)\n', (66172, 66190), False, 'import json\n'), ((67445, 67473), 'json.loads', 'json.loads', (['response.content'], {}), '(response.content)\n', (67455, 67473), False, 'import json\n'), ((68743, 68773), 'projectroles.models.RoleAssignment.objects.count', 'RoleAssignment.objects.count', ([], {}), '()\n', (68771, 68773), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((69189, 69219), 'projectroles.models.RoleAssignment.objects.count', 'RoleAssignment.objects.count', ([], {}), '()\n', (69217, 69219), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((69930, 69958), 'json.loads', 'json.loads', (['response.content'], {}), '(response.content)\n', (69940, 69958), False, 'import json\n'), ((72096, 72126), 'projectroles.models.RoleAssignment.objects.count', 'RoleAssignment.objects.count', ([], {}), '()\n', (72124, 72126), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((72477, 72507), 'projectroles.models.RoleAssignment.objects.count', 'RoleAssignment.objects.count', ([], {}), '()\n', (72505, 72507), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((73028, 73058), 'projectroles.models.RoleAssignment.objects.count', 'RoleAssignment.objects.count', ([], {}), '()\n', (73056, 73058), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((73519, 73549), 'projectroles.models.RoleAssignment.objects.count', 'RoleAssignment.objects.count', ([], {}), '()\n', (73547, 73549), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((73704, 73734), 'projectroles.models.RoleAssignment.objects.count', 'RoleAssignment.objects.count', ([], {}), '()\n', (73732, 73734), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((74087, 74117), 'projectroles.models.RoleAssignment.objects.count', 'RoleAssignment.objects.count', ([], {}), '()\n', (74115, 74117), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((74842, 74872), 'projectroles.models.RoleAssignment.objects.count', 'RoleAssignment.objects.count', ([], {}), '()\n', (74870, 74872), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((75223, 75253), 'projectroles.models.RoleAssignment.objects.count', 'RoleAssignment.objects.count', ([], {}), '()\n', (75251, 75253), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((86034, 86062), 'json.loads', 'json.loads', (['response.content'], {}), '(response.content)\n', (86044, 86062), False, 'import json\n'), ((106051, 106128), 'django.urls.reverse', 'reverse', (['"""projectroles:api_remote_get"""'], {'kwargs': "{'secret': REMOTE_SITE_SECRET}"}), "('projectroles:api_remote_get', kwargs={'secret': REMOTE_SITE_SECRET})\n", (106058, 106128), False, 'from django.urls import reverse\n'), ((50388, 50416), 'json.loads', 'json.loads', (['response.content'], {}), '(response.content)\n', (50398, 50416), False, 'import json\n'), ((56342, 56449), 'projectroles.models.RoleAssignment.objects.filter', 'RoleAssignment.objects.filter', ([], {'project': 'self.project', 'role': 'self.role_contributor', 'user': 'self.assign_user'}), '(project=self.project, role=self.\n    role_contributor, user=self.assign_user)\n', (56371, 56449), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((58362, 58465), 'projectroles.models.RoleAssignment.objects.filter', 'RoleAssignment.objects.filter', ([], {'project': 'self.project', 'role': 'self.role_delegate', 'user': 'self.assign_user'}), '(project=self.project, role=self.role_delegate,\n    user=self.assign_user)\n', (58391, 58465), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((61016, 61119), 'projectroles.models.RoleAssignment.objects.filter', 'RoleAssignment.objects.filter', ([], {'project': 'self.project', 'role': 'self.role_delegate', 'user': 'self.assign_user'}), '(project=self.project, role=self.role_delegate,\n    user=self.assign_user)\n', (61045, 61119), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((81212, 81226), 'projectroles.utils.build_secret', 'build_secret', ([], {}), '()\n', (81224, 81226), False, 'from projectroles.utils import build_secret\n'), ((81477, 81491), 'projectroles.utils.build_secret', 'build_secret', ([], {}), '()\n', (81489, 81491), False, 'from projectroles.utils import build_secret\n'), ((3458, 3491), 'pytz.timezone', 'pytz.timezone', (['settings.TIME_ZONE'], {}), '(settings.TIME_ZONE)\n', (3471, 3491), False, 'import pytz\n'), ((25539, 25633), 'projectroles.models.RoleAssignment.objects.filter', 'RoleAssignment.objects.filter', ([], {'project': 'new_category', 'user': 'self.user', 'role': 'self.role_owner'}), '(project=new_category, user=self.user, role=\n    self.role_owner)\n', (25568, 25633), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((27809, 27903), 'projectroles.models.RoleAssignment.objects.filter', 'RoleAssignment.objects.filter', ([], {'project': 'new_category', 'user': 'self.user', 'role': 'self.role_owner'}), '(project=new_category, user=self.user, role=\n    self.role_owner)\n', (27838, 27903), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((30083, 30176), 'projectroles.models.RoleAssignment.objects.filter', 'RoleAssignment.objects.filter', ([], {'project': 'new_project', 'user': 'self.user', 'role': 'self.role_owner'}), '(project=new_project, user=self.user, role=\n    self.role_owner)\n', (30112, 30176), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((55658, 55709), 'projectroles.models.RoleAssignment.objects.filter', 'RoleAssignment.objects.filter', ([], {'project': 'self.project'}), '(project=self.project)\n', (55687, 55709), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((56226, 56277), 'projectroles.models.RoleAssignment.objects.filter', 'RoleAssignment.objects.filter', ([], {'project': 'self.project'}), '(project=self.project)\n', (56255, 56277), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((57678, 57729), 'projectroles.models.RoleAssignment.objects.filter', 'RoleAssignment.objects.filter', ([], {'project': 'self.project'}), '(project=self.project)\n', (57707, 57729), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((58246, 58297), 'projectroles.models.RoleAssignment.objects.filter', 'RoleAssignment.objects.filter', ([], {'project': 'self.project'}), '(project=self.project)\n', (58275, 58297), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((60901, 60952), 'projectroles.models.RoleAssignment.objects.filter', 'RoleAssignment.objects.filter', ([], {'project': 'self.project'}), '(project=self.project)\n', (60930, 60952), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((61906, 61957), 'projectroles.models.RoleAssignment.objects.filter', 'RoleAssignment.objects.filter', ([], {'project': 'self.project'}), '(project=self.project)\n', (61935, 61957), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((62474, 62525), 'projectroles.models.RoleAssignment.objects.filter', 'RoleAssignment.objects.filter', ([], {'project': 'self.project'}), '(project=self.project)\n', (62503, 62525), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((62926, 62977), 'projectroles.models.RoleAssignment.objects.filter', 'RoleAssignment.objects.filter', ([], {'project': 'self.project'}), '(project=self.project)\n', (62955, 62977), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((63741, 63792), 'projectroles.models.RoleAssignment.objects.filter', 'RoleAssignment.objects.filter', ([], {'project': 'self.project'}), '(project=self.project)\n', (63770, 63792), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((64309, 64360), 'projectroles.models.RoleAssignment.objects.filter', 'RoleAssignment.objects.filter', ([], {'project': 'self.project'}), '(project=self.project)\n', (64338, 64360), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((72550, 72624), 'projectroles.models.RoleAssignment.objects.filter', 'RoleAssignment.objects.filter', ([], {'project': 'self.project', 'user': 'self.assign_user'}), '(project=self.project, user=self.assign_user)\n', (72579, 72624), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((84559, 84609), 'projectroles.models.ProjectInvite.objects.filter', 'ProjectInvite.objects.filter', ([], {'project': 'self.project'}), '(project=self.project)\n', (84587, 84609), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((85153, 85203), 'projectroles.models.ProjectInvite.objects.filter', 'ProjectInvite.objects.filter', ([], {'project': 'self.project'}), '(project=self.project)\n', (85181, 85203), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((86294, 86344), 'projectroles.models.ProjectInvite.objects.filter', 'ProjectInvite.objects.filter', ([], {'project': 'self.project'}), '(project=self.project)\n', (86322, 86344), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((86875, 86925), 'projectroles.models.ProjectInvite.objects.filter', 'ProjectInvite.objects.filter', ([], {'project': 'self.project'}), '(project=self.project)\n', (86903, 86925), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((87159, 87209), 'projectroles.models.ProjectInvite.objects.filter', 'ProjectInvite.objects.filter', ([], {'project': 'self.project'}), '(project=self.project)\n', (87187, 87209), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((87743, 87793), 'projectroles.models.ProjectInvite.objects.filter', 'ProjectInvite.objects.filter', ([], {'project': 'self.project'}), '(project=self.project)\n', (87771, 87793), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((88331, 88381), 'projectroles.models.ProjectInvite.objects.filter', 'ProjectInvite.objects.filter', ([], {'project': 'self.project'}), '(project=self.project)\n', (88359, 88381), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((88969, 89019), 'projectroles.models.ProjectInvite.objects.filter', 'ProjectInvite.objects.filter', ([], {'project': 'self.project'}), '(project=self.project)\n', (88997, 89019), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((89401, 89451), 'projectroles.models.ProjectInvite.objects.filter', 'ProjectInvite.objects.filter', ([], {'project': 'self.project'}), '(project=self.project)\n', (89429, 89451), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((89985, 90035), 'projectroles.models.ProjectInvite.objects.filter', 'ProjectInvite.objects.filter', ([], {'project': 'self.project'}), '(project=self.project)\n', (90013, 90035), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((90286, 90336), 'projectroles.models.ProjectInvite.objects.filter', 'ProjectInvite.objects.filter', ([], {'project': 'self.project'}), '(project=self.project)\n', (90314, 90336), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((90871, 90921), 'projectroles.models.ProjectInvite.objects.filter', 'ProjectInvite.objects.filter', ([], {'project': 'self.project'}), '(project=self.project)\n', (90899, 90921), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((91275, 91325), 'projectroles.models.ProjectInvite.objects.filter', 'ProjectInvite.objects.filter', ([], {'project': 'self.project'}), '(project=self.project)\n', (91303, 91325), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((91869, 91919), 'projectroles.models.ProjectInvite.objects.filter', 'ProjectInvite.objects.filter', ([], {'project': 'self.project'}), '(project=self.project)\n', (91897, 91919), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((92724, 92774), 'projectroles.models.ProjectInvite.objects.filter', 'ProjectInvite.objects.filter', ([], {'project': 'self.project'}), '(project=self.project)\n', (92752, 92774), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((93318, 93368), 'projectroles.models.ProjectInvite.objects.filter', 'ProjectInvite.objects.filter', ([], {'project': 'self.project'}), '(project=self.project)\n', (93346, 93368), False, 'from projectroles.models import Project, Role, RoleAssignment, ProjectInvite, SODAR_CONSTANTS\n'), ((106672, 106686), 'projectroles.utils.build_secret', 'build_secret', ([], {}), '()\n', (106684, 106686), False, 'from projectroles.utils import build_secret\n')]