Skip to content

Commit

Permalink
Merge pull request #1253 from dandi/asset-paths-pagination
Browse files Browse the repository at this point in the history
  • Loading branch information
jjnesbitt authored Aug 25, 2022
2 parents e5d39fc + e25e8d2 commit af677c7
Showing 1 changed file with 57 additions and 57 deletions.
114 changes: 57 additions & 57 deletions dandiapi/api/views/asset.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

import os.path
from pathlib import PurePosixPath
from urllib.parse import urlencode

from django.conf import settings
from django.core.paginator import EmptyPage, Page, Paginator
Expand All @@ -33,6 +32,7 @@
from rest_framework.exceptions import NotAuthenticated, PermissionDenied, ValidationError
from rest_framework.generics import get_object_or_404
from rest_framework.response import Response
from rest_framework.utils.urls import replace_query_param
from rest_framework.viewsets import GenericViewSet, ReadOnlyModelViewSet
from rest_framework_extensions.mixins import DetailSerializerMixin, NestedViewSetMixin

Expand All @@ -56,54 +56,6 @@
)


def _paginate_asset_paths(
items: list, page: int, page_size: int, versions__dandiset__pk: str, versions__version: str
) -> dict:
"""Paginates a list of files and folders to be returned by the asset paths endpoint."""
if page_size > DandiPagination.max_page_size:
page_size = DandiPagination.max_page_size

asset_count: int = len(items)

paths_paginator: Paginator = Paginator(list(items.items()), page_size)

try:
assets_page: Page = paths_paginator.page(page)
except EmptyPage:
raise Http404()

# Divide into folders and files
folders = {}
files = {}

# Note that this loop runs in constant time, since the length of assets_page
# will never exceed DandiPagination.max_page_size.
for k, v in dict(assets_page).items():
if isinstance(v, Asset):
files[k] = v
else:
folders[k] = v

paths = AssetPathsResponseSerializer({'folders': folders, 'files': files})

# generate other parameters for the paginated response
url_kwargs = {
'versions__dandiset__pk': versions__dandiset__pk,
'versions__version': versions__version,
}
url = reverse('asset-paths', kwargs=url_kwargs)
next_page = page + 1 if page + 1 <= paths_paginator.num_pages else None
prev_page = page - 1 if page - 1 > 0 else None
next_url = None
prev_url = None
if next_page is not None:
next_url = f'{url}?{urlencode({"page": next_page, "page_size": page_size})}'
if prev_page is not None:
prev_url = f'{url}?{urlencode({"page": prev_page, "page_size": page_size})}'

return {'results': paths.data, 'count': asset_count, 'next': next_url, 'previous': prev_url}


def _maybe_validate_asset_metadata(asset: Asset):
if asset.is_blob:
blob = asset.blob
Expand Down Expand Up @@ -279,6 +231,59 @@ def raise_if_unauthorized(self):
# The user does not have ownership permission
raise PermissionDenied()

def _paginate_asset_paths(self, items: dict) -> dict:
"""Paginates a list of files and folders to be returned by the asset paths endpoint."""
asset_count = len(items)
page_number = int(self.request.query_params.get(DandiPagination.page_query_param, 1))
page_size = min(
int(
self.request.query_params.get(
DandiPagination.page_size_query_param, DandiPagination.page_size
)
),
DandiPagination.max_page_size,
)

paths_paginator: Paginator = Paginator(list(items.items()), page_size)
try:
assets_page: Page = paths_paginator.page(page_number)
except EmptyPage:
raise Http404()

# Divide into folders and files
folders = {}
files = {}

# Note that this loop runs in constant time, since the length of assets_page
# will never exceed DandiPagination.max_page_size.
for k, v in dict(assets_page).items():
if isinstance(v, Asset):
files[k] = v
else:
folders[k] = v

paths = AssetPathsResponseSerializer({'folders': folders, 'files': files})

# Update url
url = self.request.build_absolute_uri()
next_url = (
replace_query_param(url, DandiPagination.page_query_param, page_number + 1)
if page_number + 1 <= paths_paginator.num_pages
else None
)
prev_url = (
replace_query_param(url, DandiPagination.page_query_param, page_number - 1)
if page_number - 1 > 0
else None
)

return {
'count': asset_count,
'next': next_url,
'previous': prev_url,
'results': paths.data,
}

def asset_from_request(self) -> Asset:
"""
Return an unsaved Asset, constructed from the request data.
Expand Down Expand Up @@ -553,8 +558,8 @@ def list(self, request, *args, **kwargs):
manual_parameters=[PATH_PREFIX_PARAM],
responses={200: AssetPathsResponseSerializer()},
)
@action(detail=False, methods=['GET'])
def paths(self, request, versions__dandiset__pk: str, versions__version: str, **kwargs):
@action(detail=False, methods=['GET'], filter_backends=[])
def paths(self, *args, **kwargs):
"""
Return the unique files/directories that directly reside under the specified path.
Expand All @@ -565,9 +570,6 @@ def paths(self, request, versions__dandiset__pk: str, versions__version: str, **
query_serializer.is_valid(raise_exception=True)

path_prefix: str = query_serializer.validated_data['path_prefix']
page: int = query_serializer.validated_data['page']
page_size: int = query_serializer.validated_data['page_size']

qs: QuerySet[Asset] = (
self.get_queryset()
.select_related('blob', 'embargoed_blob', 'zarr')
Expand Down Expand Up @@ -608,9 +610,7 @@ def paths(self, request, versions__dandiset__pk: str, versions__version: str, **

items = folders
items.update(files)
resp = _paginate_asset_paths(
items, page, page_size, versions__dandiset__pk, versions__version
)
resp = self._paginate_asset_paths(items)
return Response(resp)

# TODO: add create to forge an asset from a validation

0 comments on commit af677c7

Please sign in to comment.