Skip to content

Commit

Permalink
Merge pull request #1197 from rafalp/add-cursor-paginator
Browse files Browse the repository at this point in the history
Add cursor-based pagination
  • Loading branch information
rafalp committed Feb 5, 2019
2 parents 1974dac + e4566d9 commit c3ce882
Show file tree
Hide file tree
Showing 23 changed files with 310 additions and 233 deletions.
10 changes: 10 additions & 0 deletions dev
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ intro() {
echo " ${BOLD}psql${NORMAL} runs psql connected to development database."
echo " ${BOLD}pyfmt${NORMAL} runs isort + black on python code."
echo " ${BOLD}fakedata${NORMAL} populates database with testing data."
echo " ${BOLD}fakebigdata${NORMAL} populates database with LARGE amount of testing data."
echo
}

Expand Down Expand Up @@ -271,6 +272,13 @@ create_fake_data() {
docker-compose run --rm misago python manage.py createfakehistory 600
}

# Shortcut for creating big dev forum
create_fake_bigdata() {
docker-compose run --rm misago python manage.py createfakecategories 48
docker-compose run --rm misago python manage.py createfakecategories 24 1
docker-compose run --rm misago python manage.py createfakehistory 2190 120
}

# Command dispatcher
if [[ $1 ]]; then
if [[ $1 = "init" ]]; then
Expand Down Expand Up @@ -319,6 +327,8 @@ if [[ $1 ]]; then
black devproject misago
elif [[ $1 = "fakedata" ]]; then
create_fake_data
elif [[ $1 = "fakebigdata" ]]; then
create_fake_bigdata
else
invalid_argument $1
fi
Expand Down
16 changes: 8 additions & 8 deletions frontend/src/components/profile/feed/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,28 +22,28 @@ export function Threads(props) {
const message = ngettext(
"You have started %(threads)s thread.",
"You have started %(threads)s threads.",
props.posts.count
props.profile.threads
)

header = interpolate(
message,
{
threads: props.posts.count
threads: props.profile.threads
},
true
)
} else {
const message = ngettext(
"%(username)s has started %(threads)s thread.",
"%(username)s has started %(threads)s threads.",
props.posts.count
props.profile.threads
)

header = interpolate(
message,
{
username: props.profile.username,
threads: props.posts.count
threads: props.profile.threads
},
true
)
Expand Down Expand Up @@ -81,28 +81,28 @@ export function Posts(props) {
const message = ngettext(
"You have posted %(posts)s message.",
"You have posted %(posts)s messages.",
props.posts.count
props.profile.posts
)

header = interpolate(
message,
{
posts: props.posts.count
posts: props.profile.posts
},
true
)
} else {
const message = ngettext(
"%(username)s has posted %(posts)s message.",
"%(username)s has posted %(posts)s messages.",
props.posts.count
props.profile.posts
)

header = interpolate(
message,
{
username: props.profile.username,
posts: props.posts.count
posts: props.profile.posts
},
true
)
Expand Down
22 changes: 8 additions & 14 deletions frontend/src/components/profile/feed/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ export default class extends React.Component {
}
}

loadItems(page = 1) {
loadItems(start = 0) {
ajax
.get(this.props.api, {
page: page || 1
start: start || 0
})
.then(
data => {
if (page === 1) {
if (start === 0) {
store.dispatch(posts.load(data))
} else {
store.dispatch(posts.append(data))
Expand All @@ -48,7 +48,7 @@ export default class extends React.Component {
isLoading: true
})

this.loadItems(this.props.posts.page + 1)
this.loadItems(this.props.posts.next)
}

componentDidMount() {
Expand Down Expand Up @@ -77,7 +77,7 @@ export default class extends React.Component {
}

export function Feed(props) {
if (!props.posts.count) {
if (!props.posts.results.length) {
return <p className="lead">{props.emptyMessage}</p>
}

Expand All @@ -91,14 +91,14 @@ export function Feed(props) {
<LoadMoreButton
isLoading={props.isLoading}
loadMore={props.loadMore}
more={props.posts.more}
next={props.posts.next}
/>
</div>
)
}

export function LoadMoreButton(props) {
if (!props.more) return null
if (!props.next) return null

return (
<div className="pager-more">
Expand All @@ -107,13 +107,7 @@ export function LoadMoreButton(props) {
loading={props.isLoading}
onClick={props.loadMore}
>
{interpolate(
gettext("Show more (%(more)s)"),
{
more: props.more
},
true
)}
{gettext("Show older activity")}
</Button>
</div>
)
Expand Down
58 changes: 21 additions & 37 deletions frontend/src/components/threads/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,8 @@ export default class extends WithDropdown {

dropdown: false,
subcategories: [],

count: 0,
more: 0,

page: 1,
pages: 1

next: 0,
}

let category = this.getCategory()
Expand All @@ -72,14 +68,8 @@ export default class extends WithDropdown {
initWithPreloadedData(category, data) {
this.state = Object.assign(this.state, {
moderation: getModerationActions(data.results),

subcategories: data.subcategories,

count: data.count,
more: data.more,

page: data.page,
pages: data.pages
next: data.next
})

this.startPolling(category)
Expand All @@ -89,14 +79,14 @@ export default class extends WithDropdown {
this.loadThreads(category)
}

loadThreads(category, page = 1) {
loadThreads(category, next = 0) {
ajax
.get(
this.props.options.api,
{
category: category,
list: this.props.route.list.type,
page: page || 1
start: next || 0
},
"threads"
)
Expand All @@ -107,7 +97,7 @@ export default class extends WithDropdown {
return
}

if (page === 1) {
if (next === 0) {
store.dispatch(hydrate(data.results))
} else {
store.dispatch(append(data.results, this.getSorting()))
Expand All @@ -121,11 +111,7 @@ export default class extends WithDropdown {

subcategories: data.subcategories,

count: data.count,
more: data.more,

page: data.page,
pages: data.pages
next: data.next,
})

this.startPolling(category)
Expand Down Expand Up @@ -207,7 +193,7 @@ export default class extends WithDropdown {
isBusy: true
})

this.loadThreads(this.getCategory(), this.state.page + 1)
this.loadThreads(this.getCategory(), this.state.next)
}

pollResponse = data => {
Expand Down Expand Up @@ -255,21 +241,19 @@ export default class extends WithDropdown {
}

getMoreButton() {
if (this.state.more) {
return (
<div className="pager-more">
<Button
className="btn btn-default btn-outline"
loading={this.state.isBusy || this.state.busyThreads.length}
onClick={this.loadMore}
>
{gettext("Show more")}
</Button>
</div>
)
} else {
return null
}
if (!this.state.next) return null

return (
<div className="pager-more">
<Button
className="btn btn-default btn-outline"
loading={this.state.isBusy || this.state.busyThreads.length}
onClick={this.loadMore}
>
{gettext("Show more")}
</Button>
</div>
)
}

getClassName() {
Expand Down
1 change: 0 additions & 1 deletion misago/conf/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,6 @@
# Threads lists pagination settings

MISAGO_THREADS_PER_PAGE = 25
MISAGO_THREADS_TAIL = 15


# Posts lists pagination settings
Expand Down
43 changes: 43 additions & 0 deletions misago/core/cursorpagination.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from django.core.paginator import EmptyPage, InvalidPage


def get_page(queryset, order_by, per_page, start=0):
if start < 0:
raise InvalidPage()

object_list = list(_slice_queryset(queryset, order_by, per_page, start))
if start and not object_list:
raise EmptyPage()

next_cursor = None
if len(object_list) > per_page:
next_slice_first_item = object_list.pop(-1)
attr_name = order_by.lstrip("-")
next_cursor = getattr(next_slice_first_item, attr_name)

return CursorPage(start, object_list, next_cursor)


def _slice_queryset(queryset, order_by, per_page, start):
page_len = int(per_page) + 1
if start:
if order_by.startswith("-"):
filter_name = "%s__lte" % order_by[1:]
else:
filter_name = "%s__gte" % order_by
return queryset.filter(**{filter_name: start})[:page_len]
return queryset[:page_len]


class CursorPage:
def __init__(self, start, object_list, next_=None):
self.start = start or 0
self.first = self.start == 0
self.object_list = object_list
self.next = next_

def __len__(self):
return len(self.object_list)

def has_next(self):
return bool(self.next)

0 comments on commit c3ce882

Please sign in to comment.