Skip to content

Commit e1cb456

Browse files
fix: trash page sorting and show more (#4967)
1 parent bf89efa commit e1cb456

File tree

4 files changed

+59
-33
lines changed

4 files changed

+59
-33
lines changed

contentcuration/contentcuration/frontend/channelEdit/views/trash/TrashModal.vue

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@
201201
];
202202
},
203203
items() {
204-
return sortBy(this.getContentNodeChildren(this.trashId), 'modified');
204+
return sortBy(this.getContentNodeChildren(this.trashId), 'modified').reverse();
205205
},
206206
backLink() {
207207
return {
@@ -225,9 +225,9 @@
225225
},
226226
},
227227
created() {
228-
this.loadContentNodes({ parent__in: [this.rootId] }),
229-
this.loadAncestors({ id: this.nodeId }),
230-
this.loadNodes();
228+
this.loadContentNodes({ parent__in: [this.rootId] });
229+
this.loadAncestors({ id: this.nodeId });
230+
this.loadNodes();
231231
},
232232
mounted() {
233233
this.updateTabTitle(this.$store.getters.appendChannelName(this.$tr('trashModalTitle')));
@@ -246,10 +246,12 @@
246246
this.loading = false;
247247
return;
248248
}
249-
this.loadChildren({ parent: this.trashId }).then(childrenResponse => {
250-
this.loading = false;
251-
this.more = childrenResponse.more || null;
252-
});
249+
this.loadChildren({ parent: this.trashId, ordering: '-modified' }).then(
250+
childrenResponse => {
251+
this.loading = false;
252+
this.more = childrenResponse.more || null;
253+
}
254+
);
253255
},
254256
moveNodes(target) {
255257
return this.moveContentNodes({
@@ -311,7 +313,7 @@
311313
};
312314
313315
</script>
314-
<style lang="less" scoped>
316+
<style lang="scss" scoped>
315317
316318
.show-more-button-container {
317319
display: flex;

contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/actions.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,11 @@ export function loadContentNodeByNodeId(context, nodeId) {
7171
});
7272
}
7373

74-
export function loadChildren(context, { parent, published = null, complete = null }) {
75-
const params = { parent, max_results: 25 };
74+
export function loadChildren(
75+
context,
76+
{ parent, published = null, complete = null, ordering = null }
77+
) {
78+
const params = { parent, max_results: 25, ordering };
7679
if (published !== null) {
7780
params.published = published;
7881
}

contentcuration/contentcuration/frontend/shared/data/resources.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,14 @@ class IndexedDBResource {
541541
collection = collection.filter(filterFn);
542542
}
543543
if (paginationActive) {
544+
// Default pagination field is 'lft'
545+
let paginationField = 'lft';
546+
if (params.ordering) {
547+
paginationField = params.ordering.replace(/^-/, '');
548+
}
549+
// Determine the operator based on the ordering direction.
550+
const operator = params.ordering && params.ordering.startsWith('-') ? 'lt' : 'gt';
551+
544552
let results;
545553
if (sortBy) {
546554
// If we still have a sortBy value here, then we have not sorted using orderBy
@@ -558,7 +566,8 @@ class IndexedDBResource {
558566
more: hasMore
559567
? {
560568
...params,
561-
lft__gt: results[maxResults - 1].lft,
569+
// Dynamically set the pagination cursor based on the pagination field and operator.
570+
[`${paginationField}__${operator}`]: results[maxResults - 1][paginationField],
562571
}
563572
: null,
564573
};

contentcuration/contentcuration/viewsets/contentnode.py

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -669,50 +669,62 @@ def dict_if_none(obj, field_name=None):
669669

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

679+
def get_pagination_params(self):
680+
# Default ordering is "lft" if not provided.
681+
ordering_param = self.request.query_params.get("ordering", "lft")
682+
# Remove the leading '-' if present to get the field name.
683+
pagination_field = ordering_param.lstrip("-")
684+
# Determine operator: if ordering starts with '-', use __lt; otherwise __gt.
685+
operator = "__lt" if ordering_param.startswith("-") else "__gt"
686+
return pagination_field, operator
687+
683688
def decode_cursor(self, request):
684689
"""
685-
Given a request with a cursor, return a `Cursor` instance.
690+
Given a request with a cursor parameter, return a `Cursor` instance.
691+
The cursor parameter name is dynamically built from the pagination field and operator.
686692
"""
687-
# Determine if we have a cursor, and if so then decode it.
688-
value = request.query_params.get(self.cursor_query_param)
693+
pagination_field, operator = self.get_pagination_params()
694+
cursor_param = f"{pagination_field}{operator}"
695+
value = request.query_params.get(cursor_param)
689696
if value is None:
690697
return None
691-
692-
try:
693-
value = int(value)
694-
except ValueError:
695-
raise ValidationError("lft must be an integer but an invalid value was given.")
698+
if pagination_field == "lft":
699+
try:
700+
value = int(value)
701+
except ValueError:
702+
raise ValidationError("lft must be an integer but an invalid value was given.")
696703

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

699706
def encode_cursor(self, cursor):
700707
"""
701-
Given a Cursor instance, return an url with query parameter.
708+
Given a Cursor instance, return a URL with the dynamic pagination cursor query parameter.
702709
"""
703-
return replace_query_param(self.base_url, self.cursor_query_param, str(cursor.position))
710+
pagination_field, operator = self.get_pagination_params()
711+
cursor_param = f"{pagination_field}{operator}"
712+
return replace_query_param(self.base_url, cursor_param, str(cursor.position))
704713

705714
def get_more(self):
715+
"""
716+
Construct a "more" URL (or query parameters) that includes the pagination cursor
717+
built from the dynamic field and operator.
718+
"""
719+
pagination_field, operator = self.get_pagination_params()
720+
cursor_param = f"{pagination_field}{operator}"
706721
position, offset = self._get_more_position_offset()
707722
if position is None and offset is None:
708723
return None
709724
params = self.request.query_params.copy()
710-
params.update({
711-
self.cursor_query_param: position,
712-
})
725+
params.update({cursor_param: position})
713726
return params
714727

715-
716728
# Apply mixin first to override ValuesViewset
717729
class ContentNodeViewSet(BulkUpdateMixin, ValuesViewset):
718730
queryset = ContentNode.objects.all()

0 commit comments

Comments
 (0)