Skip to content

fix: trash page sorting and show more #4967

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: hotfixes
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@
];
},
items() {
return sortBy(this.getContentNodeChildren(this.trashId), 'modified');
return sortBy(this.getContentNodeChildren(this.trashId), 'modified').reverse();
},
backLink() {
return {
Expand All @@ -225,9 +225,9 @@
},
},
created() {
this.loadContentNodes({ parent__in: [this.rootId] }),
this.loadAncestors({ id: this.nodeId }),
this.loadNodes();
this.loadContentNodes({ parent__in: [this.rootId] });
this.loadAncestors({ id: this.nodeId });
this.loadNodes();
},
mounted() {
this.updateTabTitle(this.$store.getters.appendChannelName(this.$tr('trashModalTitle')));
Expand All @@ -246,10 +246,12 @@
this.loading = false;
return;
}
this.loadChildren({ parent: this.trashId }).then(childrenResponse => {
this.loading = false;
this.more = childrenResponse.more || null;
});
this.loadChildren({ parent: this.trashId, ordering: '-modified' }).then(
childrenResponse => {
this.loading = false;
this.more = childrenResponse.more || null;
}
);
},
moveNodes(target) {
return this.moveContentNodes({
Expand Down Expand Up @@ -311,7 +313,7 @@
};

</script>
<style lang="less" scoped>
<style lang="scss" scoped>

.show-more-button-container {
display: flex;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,11 @@ export function loadContentNodeByNodeId(context, nodeId) {
});
}

export function loadChildren(context, { parent, published = null, complete = null }) {
const params = { parent, max_results: 25 };
export function loadChildren(
context,
{ parent, published = null, complete = null, ordering = null }
) {
const params = { parent, max_results: 25, ordering };
if (published !== null) {
params.published = published;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,14 @@ class IndexedDBResource {
collection = collection.filter(filterFn);
}
if (paginationActive) {
// Default pagination field is 'lft'
let paginationField = 'lft';
if (params.ordering) {
paginationField = params.ordering.replace(/^-/, '');
}
// Determine the operator based on the ordering direction.
const operator = params.ordering && params.ordering.startsWith('-') ? 'lt' : 'gt';

let results;
if (sortBy) {
// If we still have a sortBy value here, then we have not sorted using orderBy
Expand All @@ -558,7 +566,8 @@ class IndexedDBResource {
more: hasMore
? {
...params,
lft__gt: results[maxResults - 1].lft,
// Dynamically set the pagination cursor based on the pagination field and operator.
[`${paginationField}__${operator}`]: results[maxResults - 1][paginationField],
}
: null,
};
Expand Down
54 changes: 33 additions & 21 deletions contentcuration/contentcuration/viewsets/contentnode.py
Original file line number Diff line number Diff line change
Expand Up @@ -669,50 +669,62 @@ def dict_if_none(obj, field_name=None):

class ContentNodePagination(ValuesViewsetCursorPagination):
"""
A simplified cursor pagination class for ContentNodeViewSet.
Instead of using an opaque cursor, it uses the lft value for filtering.
As such, if this pagination scheme is used without applying a filter
that will guarantee membership to a specific MPTT tree, such as parent
or tree_id, the pagination scheme will not be predictable.
A simplified cursor pagination class
Instead of using a fixed 'lft' cursor, it dynamically sets the pagination field and operator
based on the incoming `ordering` query parameter.
"""
cursor_query_param = "lft__gt"
ordering = "lft"
page_size_query_param = "max_results"
max_page_size = 100

def get_pagination_params(self):
# Default ordering is "lft" if not provided.
ordering_param = self.request.query_params.get("ordering", "lft")
# Remove the leading '-' if present to get the field name.
pagination_field = ordering_param.lstrip("-")
# Determine operator: if ordering starts with '-', use __lt; otherwise __gt.
operator = "__lt" if ordering_param.startswith("-") else "__gt"
return pagination_field, operator

def decode_cursor(self, request):
"""
Given a request with a cursor, return a `Cursor` instance.
Given a request with a cursor parameter, return a `Cursor` instance.
The cursor parameter name is dynamically built from the pagination field and operator.
"""
# Determine if we have a cursor, and if so then decode it.
value = request.query_params.get(self.cursor_query_param)
pagination_field, operator = self.get_pagination_params()
cursor_param = f"{pagination_field}{operator}"
value = request.query_params.get(cursor_param)
if value is None:
return None

try:
value = int(value)
except ValueError:
raise ValidationError("lft must be an integer but an invalid value was given.")
if pagination_field == "lft":
try:
value = int(value)
except ValueError:
raise ValidationError("lft must be an integer but an invalid value was given.")

return Cursor(offset=0, reverse=False, position=value)

def encode_cursor(self, cursor):
"""
Given a Cursor instance, return an url with query parameter.
Given a Cursor instance, return a URL with the dynamic pagination cursor query parameter.
"""
return replace_query_param(self.base_url, self.cursor_query_param, str(cursor.position))
pagination_field, operator = self.get_pagination_params()
cursor_param = f"{pagination_field}{operator}"
return replace_query_param(self.base_url, cursor_param, str(cursor.position))

def get_more(self):
"""
Construct a "more" URL (or query parameters) that includes the pagination cursor
built from the dynamic field and operator.
"""
pagination_field, operator = self.get_pagination_params()
cursor_param = f"{pagination_field}{operator}"
position, offset = self._get_more_position_offset()
if position is None and offset is None:
return None
params = self.request.query_params.copy()
params.update({
self.cursor_query_param: position,
})
params.update({cursor_param: position})
return params


# Apply mixin first to override ValuesViewset
class ContentNodeViewSet(BulkUpdateMixin, ValuesViewset):
queryset = ContentNode.objects.all()
Expand Down