Skip to content

Commit

Permalink
add link_to_external_reports_archive
Browse files Browse the repository at this point in the history
  • Loading branch information
aaxelb committed Aug 20, 2024
1 parent 3920a29 commit 5bf4ee4
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 41 deletions.
12 changes: 12 additions & 0 deletions api/base/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,18 @@ def should_show(self, instance):
return request and (request.user.is_anonymous or has_admin_scope)


class ShowIfObjectPermission(ConditionalField):
"""Show the field only for users with a given object permission
"""
def __init__(self, field, *, permission: str, **kwargs):
super().__init__(field, **kwargs)
self._required_object_permission = permission

def should_show(self, instance):
_request = self.context.get('request')
return _request.user.has_perm(self._required_object_permission, obj=instance)


class HideIfRegistration(ConditionalField):
"""
If node is a registration, this field will return None.
Expand Down
32 changes: 23 additions & 9 deletions api/institutions/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
BaseAPISerializer,
ShowIfVersion,
IDField,
ShowIfObjectPermission,
)

from api.nodes.serializers import CompoundIDField
Expand All @@ -35,6 +36,10 @@ class InstitutionSerializer(JSONAPISerializer):
ror_iri = ser.CharField(read_only=True, source='ror_uri')
iris = ser.SerializerMethodField(read_only=True)
assets = ser.SerializerMethodField(read_only=True)
link_to_external_reports_archive = ShowIfObjectPermission(
ser.CharField(read_only=True),
permission='view_institutional_metrics',
)
links = LinksField({
'self': 'get_api_url',
'html': 'get_absolute_html_url',
Expand All @@ -55,19 +60,28 @@ class InstitutionSerializer(JSONAPISerializer):
related_view_kwargs={'institution_id': '<_id>'},
)

department_metrics = RelationshipField(
related_view='institutions:institution-department-metrics',
related_view_kwargs={'institution_id': '<_id>'},
department_metrics = ShowIfObjectPermission(
RelationshipField(
related_view='institutions:institution-department-metrics',
related_view_kwargs={'institution_id': '<_id>'},
),
permission='view_institutional_metrics',
)

user_metrics = RelationshipField(
related_view='institutions:institution-user-metrics',
related_view_kwargs={'institution_id': '<_id>'},
user_metrics = ShowIfObjectPermission(
RelationshipField(
related_view='institutions:institution-user-metrics',
related_view_kwargs={'institution_id': '<_id>'},
),
permission='view_institutional_metrics',
)

summary_metrics = RelationshipField(
related_view='institutions:institution-summary-metrics',
related_view_kwargs={'institution_id': '<_id>'},
summary_metrics = ShowIfObjectPermission(
RelationshipField(
related_view='institutions:institution-summary-metrics',
related_view_kwargs={'institution_id': '<_id>'},
),
permission='view_institutional_metrics',
)

def get_api_url(self, obj):
Expand Down
90 changes: 58 additions & 32 deletions api_tests/institutions/views/test_institution_detail.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import pytest

from osf_tests.factories import InstitutionFactory
from osf_tests.factories import (
AuthUserFactory,
InstitutionFactory,
)
from api.base.settings.defaults import API_BASE
from django.core.validators import URLValidator

Expand All @@ -11,6 +14,8 @@ class TestInstitutionDetail:
'nodes',
'registrations',
'users',
}
expected_metrics_relationships = {
'department_metrics',
'user_metrics',
'summary_metrics'
Expand All @@ -26,34 +31,55 @@ def institution(self):
def url(self, institution):
return f'/{API_BASE}institutions/{institution._id}/'

def test_detail_response(self, app, institution, url):

# 404 on wrong _id
res = app.get(f'/{institution}institutions/1PO/', expect_errors=True)
assert res.status_code == 404

res = app.get(url)
assert res.status_code == 200
attrs = res.json['data']['attributes']
assert attrs['name'] == institution.name
assert attrs['iri'] == institution.identifier_domain
assert attrs['ror_iri'] == institution.ror_uri
assert set(attrs['iris']) == {
institution.ror_uri,
institution.identifier_domain,
institution.absolute_url,
}
assert 'logo_path' in attrs
assert set(attrs['assets'].keys()) == {'logo', 'logo_rounded', 'banner'}
assert res.json['data']['links']['self'].endswith(url)

relationships = res.json['data']['relationships']
assert self.expected_relationships == set(relationships.keys())
for relationships in list(relationships.values()):
# ↓ returns None if url is valid else throws error.
assert self.is_valid_url(relationships['links']['related']['href']) is None

# test_return_without_logo_path
res = app.get(f'{url}?version=2.14')
assert res.status_code == 200
assert 'logo_path' not in res.json['data']['attributes']
@pytest.fixture()
def rando(self):
return AuthUserFactory()

@pytest.fixture()
def institutional_admin(self, institution):
_admin_user = AuthUserFactory()
institution.get_group('institutional_admins').user_set.add(_admin_user)
return _admin_user

def test_detail_response(self, app, institution, url, rando, institutional_admin):

for _user in (None, rando, institutional_admin):
_auth = (None if _user is None else _user.auth)
# 404 on wrong _id
res = app.get(f'/{institution}institutions/1PO/', expect_errors=True, auth=_auth)
assert res.status_code == 404

res = app.get(url, auth=_auth)
assert res.status_code == 200
attrs = res.json['data']['attributes']
assert attrs['name'] == institution.name
assert attrs['iri'] == institution.identifier_domain
assert attrs['ror_iri'] == institution.ror_uri
assert set(attrs['iris']) == {
institution.ror_uri,
institution.identifier_domain,
institution.absolute_url,
}
assert 'logo_path' in attrs
assert set(attrs['assets'].keys()) == {'logo', 'logo_rounded', 'banner'}
if _user is institutional_admin:
assert attrs['link_to_external_reports_archive'] == institution.link_to_external_reports_archive
else:
assert 'link_to_external_reports_archive' not in attrs
assert res.json['data']['links']['self'].endswith(url)

relationships = res.json['data']['relationships']
_expected_relationships = (
self.expected_relationships | self.expected_metrics_relationships
if _user is institutional_admin
else self.expected_relationships
)
assert _expected_relationships == set(relationships.keys())
for relationships in list(relationships.values()):
# ↓ returns None if url is valid else throws error.
assert self.is_valid_url(relationships['links']['related']['href']) is None

# test_return_without_logo_path
res = app.get(f'{url}?version=2.14', auth=_auth)
assert res.status_code == 200
assert 'logo_path' not in res.json['data']['attributes']
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.15 on 2024-08-16 15:21

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('osf', '0022_alter_abstractnode_subjects_alter_abstractnode_tags_and_more'),
]

operations = [
migrations.AddField(
model_name='institution',
name='link_to_external_reports_archive',
field=models.URLField(blank=True, default='', help_text='Full URL where institutional admins can access archived metrics reports.', max_length=2048),
),
]
6 changes: 6 additions & 0 deletions osf/models/institution.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,12 @@ class Institution(DirtyFieldsMixin, Loggable, ObjectIDMixin, BaseModel, Guardian
blank=True,
help_text='The full domain this institutions that will appear in DOI metadata.'
)
link_to_external_reports_archive = models.URLField(
max_length=2048,
blank=True,
default='',
help_text='Full URL where institutional admins can access archived metrics reports.',
)

class Meta:
# custom permissions for use in the OSF Admin App
Expand Down

0 comments on commit 5bf4ee4

Please sign in to comment.