Skip to content

Commit

Permalink
update institutional dash board code
Browse files Browse the repository at this point in the history
  • Loading branch information
John Tordoff committed Aug 22, 2024
1 parent b882e37 commit 90bdefe
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 57 deletions.
109 changes: 90 additions & 19 deletions api/institutions/views.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from django.db.models import Count, Q, F, V
from django.db.models import Count, Q, F, Value, BooleanField, IntegerField
from django.db.models.functions import Coalesce
from rest_framework import generics
from rest_framework import permissions as drf_permissions
Expand All @@ -7,6 +7,7 @@
from rest_framework.response import Response
from rest_framework.settings import api_settings


from framework.auth.oauth_scopes import CoreScopes

from osf.metrics import InstitutionProjectCounts
Expand Down Expand Up @@ -543,30 +544,100 @@ class InstitutionDashboardUserList(JSONAPIBaseView, generics.ListAPIView, ListFi
ordering = ('-id',)

def get_default_queryset(self):
return self.get_institution().get_institution_users().annotate(
institution = self.get_institution()
from django.db.models import OuterRef, Subquery, Count, Q, F, Value, BooleanField, IntegerField
from django.db.models.functions import Coalesce
from django.db.models.expressions import RawSQL

return institution.get_institution_users().annotate(
email_address=F('username'),
department=F('institutionaffiliation__sso_department'),
# Count of public projects (assuming a related_name 'projects' from OSFUser to Project)
number_of_public_projects=Count('nodes', filter=Q(nodes__is_public=True) & Q(nodes__type='osf.node')),
number_of_private_projects=Count('nodes', filter=Q(nodes__is_public=False) & Q(nodes__type='osf.node')),
# Example for registrations, assuming a similar setup
number_of_public_registrations=Count('nodes', filter=Q(nodes__is_public=True) & Q(nodes__type='osf.registration')),
number_of_private_registrations=Count('nodes', filter=Q(nodes__is_public=False) & Q(nodes__type='osf.registration')),
# Assuming 'preprints' is a related name from OSFUser to a Preprint model
number_of_preprints=Count('preprints', distinct=True),
# Assuming there's a File model related to users for counting files

# count the files on nodes where users have WRITE perms
number_of_node_files=Count('nodes__files', distinct=True),
# Count files associated with registrations
number_of_registration_files=Count('registrations__files', distinct=True),
# Count files associated with preprints
number_of_preprint_files=Count('preprints__files', distinct=True),
number_of_public_projects=Count(
'nodes',
filter=(Q(nodes__is_public=True) & Q(nodes__type='osf.node')),
distinct=True
),
number_of_private_projects=Count(
'nodes',
filter=(Q(nodes__is_public=False) & Q(nodes__type='osf.node')),
distinct=True
),
# Count of public and private registrations
number_of_public_registrations=Count(
'nodes',
filter=(Q(nodes__is_public=True) & Q(nodes__type='osf.registration')),
distinct=True
),
number_of_private_registrations=Count(
'nodes',
filter=(Q(nodes__is_public=False) & Q(nodes__type='osf.registration')),
distinct=True
),
# Count of preprints
number_of_preprints=Count(
'preprints',
filter=Q(preprints__is_public=True),
distinct=True
),
# Count files associated with nodes
number_of_node_files=RawSQL(
"""
SELECT COUNT(f.id)
FROM osf_basefilenode f
INNER JOIN osf_abstractnode n ON n.id = f.target_object_id
INNER JOIN django_content_type ct ON ct.id = f.target_content_type_id
WHERE ct.model = 'abstractnode'
AND n.type = 'osf.node'
AND f.type = 'osf.osfstoragefile'
AND n.creator_id = osf_osfuser.id
""",
[],
output_field=IntegerField()
),
# Count files associated with registrations using RawSQL
number_of_registration_files=RawSQL(
"""
SELECT COUNT(f.id)
FROM osf_basefilenode f
INNER JOIN osf_abstractnode r ON r.id = f.target_object_id
INNER JOIN django_content_type ct ON ct.id = f.target_content_type_id
WHERE ct.model = 'abstractnode'
AND r.type = 'osf.registration'
AND f.type = 'osf.osfstoragefile'
AND r.creator_id = osf_osfuser.id
""",
[],
output_field=IntegerField()
),
# Count files associated with preprints using RawSQL
number_of_preprint_files=RawSQL(
"""
SELECT COUNT(f.id)
FROM osf_basefilenode f
INNER JOIN osf_preprint p ON p.id = f.target_object_id
INNER JOIN django_content_type ct ON ct.id = f.target_content_type_id
WHERE ct.model = 'preprint'
AND p.is_public = TRUE
AND f.type = 'osf.osfstoragefile'
AND p.creator_id = osf_osfuser.id
""",
[],
output_field=IntegerField()
),
number_of_files=Coalesce(
F('number_of_node_files') +
F('number_of_registration_files') +
F('number_of_preprint_files'), V(0)
)
F('number_of_preprint_files'),
Value(0),
output_field=IntegerField()
),
has_orcid=Coalesce(
Q(external_identity__has_key='ORCID'),
Value(False),
output_field=BooleanField()
),
account_created_date=F('created')
)

# overrides RetrieveAPIView
Expand Down
9 changes: 9 additions & 0 deletions api/users/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ class UserSerializer(JSONAPISerializer):
'number_of_private_registrations', # For Institutional Dashboard only
'number_of_preprints', # For Institutional Dashboard only
'number_of_files', # For Institutional Dashboard only
'has_orcid', # For Institutional Dashboard only
'account_created_date', # For Institutional Dashboard only
'last_log', # For Institutional Dashboard only
'full_name',
'given_name',
'middle_names',
Expand Down Expand Up @@ -102,7 +105,13 @@ class UserSerializer(JSONAPISerializer):
number_of_public_registrations = ser.IntegerField(required=False, help_text='For Institutional Dashboard only')
number_of_private_registrations = ser.IntegerField(required=False, help_text='For Institutional Dashboard only')
number_of_preprints = ser.IntegerField(required=False, help_text='For Institutional Dashboard only')
number_of_node_files = ser.IntegerField(required=False, help_text='For Institutional Dashboard only')
number_of_registration_files = ser.IntegerField(required=False, help_text='For Institutional Dashboard only')
number_of_preprint_files = ser.IntegerField(required=False, help_text='For Institutional Dashboard only')
number_of_files = ser.IntegerField(required=False, help_text='For Institutional Dashboard only')
has_orcid = ser.BooleanField(required=False, help_text='For Institutional Dashboard only')
account_created_date = VersionedDateTimeField(required=False, help_text='For Institutional Dashboard only')
last_log = VersionedDateTimeField(required=False, help_text='For Institutional Dashboard only')

links = HideIfDisabled(
LinksField(
Expand Down
67 changes: 29 additions & 38 deletions api_tests/institutions/views/test_institution_dashboard_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
PreprintFactory
)
from django.shortcuts import reverse
from osf.models import BaseFileNode


@pytest.mark.django_db
Expand Down Expand Up @@ -37,6 +38,12 @@ def institution(self):

@pytest.fixture()
def users(self, institution):
"""
User_one has two public projects and one registrations. User_two has 1 public registrations. User_three has
1 Public Registristion and 2 Preprints. So 2 public Projects, 3 registrations and 2 Preprints.
"""
from osf_tests.test_elastic_search import create_file_version

user_one = AuthUserFactory(
fullname='Alice Example',
username='[email protected]'
Expand All @@ -60,29 +67,46 @@ def users(self, institution):
creator=user_one,
is_public=True
)
project2 = ProjectFactory(
file_ = project.get_addon('osfstorage').get_root().append_file('New Test file.mp3')
create_file_version(file_, user_one)
file_.save()

ProjectFactory(
creator=user_one,
is_public=True
)
registration = RegistrationFactory(
creator=user_one,
)
registration = RegistrationFactory(
file_ = registration.get_addon('osfstorage').get_root().append_file('New Test file 2.5.mp3')
create_file_version(file_, user_one)
file_.save()

RegistrationFactory(
creator=user_two,
)
registration = RegistrationFactory(

RegistrationFactory(
creator=user_three,
)
PreprintFactory(
creator=user_three,
)
preprint = PreprintFactory(
PreprintFactory(
creator=user_three,
)
preprint = PreprintFactory(
project = ProjectFactory(
creator=user_three,
is_public=True
)
file_ = project.get_addon('osfstorage').get_root().append_file('New Test file 2.mp3')
create_file_version(file_, user_three)
file_.save()

return [user_one, user_two, user_three]

def test_return_all_users(self, app, institution, users):

url = reverse(
'institutions:institution-users-list-dashboard',
kwargs={
Expand Down Expand Up @@ -141,36 +165,3 @@ def test_sort_users(self, app, institution, users, attribute):
# Extracting sorted attribute values from response
sorted_values = [user['attributes'][attribute] for user in res.json['data']]
assert sorted_values == sorted(sorted_values), 'Values are not sorted correctly'


@pytest.mark.django_db
class TestInstitutionUsersListCSVRenderer:

def test_csv_output(self, app, institution, users):
"""
Test to ensure the CSV renderer returns data in the expected CSV format with correct headers.
"""
url = reverse(
'institutions:institution-users-list-dashboard',
kwargs={
'version': 'v2',
'institution_id': institution._id
}
) + '?format=csv'
response = app.get(url)
assert response.status_code == 200
assert response['Content-Type'] == 'text/csv'

# Read the content of the response as CSV
content = response.content.decode('utf-8')
csv_reader = csv.reader(io.StringIO(content))
headers = next(csv_reader) # First line contains headers

# Define expected headers based on the serializer used
expected_headers = ['ID', 'Email', 'Department', 'Public Projects', 'Private Projects', 'Public Registrations',
'Private Registrations', 'Preprints']
assert headers == expected_headers, "CSV headers do not match expected headers"

# Optionally, check a few lines of actual data if necessary
for row in csv_reader:
assert len(row) == len(expected_headers), "Number of data fields in CSV does not match headers"

0 comments on commit 90bdefe

Please sign in to comment.