Skip to content

Commit

Permalink
Add configuration options for pagination (#835)
Browse files Browse the repository at this point in the history
  • Loading branch information
sissbruecker authored Sep 18, 2024
1 parent 2aab281 commit 450980a
Show file tree
Hide file tree
Showing 10 changed files with 157 additions and 10 deletions.
26 changes: 26 additions & 0 deletions bookmarks/migrations/0040_userprofile_items_per_page_and_more.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Generated by Django 5.0.8 on 2024-09-18 20:11

import django.core.validators
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("bookmarks", "0039_globalsettings_enable_link_prefetch"),
]

operations = [
migrations.AddField(
model_name="userprofile",
name="items_per_page",
field=models.IntegerField(
default=30, validators=[django.core.validators.MinValueValidator(10)]
),
),
migrations.AddField(
model_name="userprofile",
name="sticky_pagination",
field=models.BooleanField(default=False),
),
]
9 changes: 8 additions & 1 deletion bookmarks/models.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import binascii
import logging
import os
from typing import List

import binascii
from django import forms
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.models import User
from django.core.validators import MinValueValidator
from django.db import models
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
Expand Down Expand Up @@ -422,6 +423,10 @@ class UserProfile(models.Model):
search_preferences = models.JSONField(default=dict, null=False)
enable_automatic_html_snapshots = models.BooleanField(default=True, null=False)
default_mark_unread = models.BooleanField(default=False, null=False)
items_per_page = models.IntegerField(
null=False, default=30, validators=[MinValueValidator(10)]
)
sticky_pagination = models.BooleanField(default=False, null=False)


class UserProfileForm(forms.ModelForm):
Expand Down Expand Up @@ -450,6 +455,8 @@ class Meta:
"default_mark_unread",
"custom_css",
"auto_tagging_rules",
"items_per_page",
"sticky_pagination",
]


Expand Down
36 changes: 36 additions & 0 deletions bookmarks/styles/bookmark-page.css
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,28 @@ li[ld-bookmark-item] {
& .page-item:first-child a {
padding-left: 0;
}

&.sticky {
position: sticky;
bottom: 0;
border-top: solid 1px var(--secondary-border-color);
background: var(--body-color);
padding-bottom: var(--unit-h);

&:before {
content: '';
position: absolute;
top: 0;
bottom: 0;
left: calc(-1 * calc(var(--bulk-edit-toggle-width) + var(--bulk-edit-toggle-offset)));
width: calc(var(--bulk-edit-toggle-width) + var(--bulk-edit-toggle-offset));
background: var(--body-color);
}
}

& .pagination {
overflow: hidden;
}
}

.tag-cloud {
Expand Down Expand Up @@ -379,6 +401,7 @@ ul.bookmark-list {
}

/* Hide section border when bulk edit bar is opened, otherwise borders overlap in dark mode due to using contrast colors */

&.active section:first-of-type .content-area-header {
border-bottom-color: transparent;
}
Expand All @@ -389,6 +412,19 @@ ul.bookmark-list {
overflow: visible;
}

/* make sticky pagination expand to cover checkboxes to the left */

&.active .bookmark-pagination.sticky:before {
content: '';
position: absolute;
top: -1px;
bottom: 0;
left: calc(-1 * calc(var(--bulk-edit-toggle-width) + var(--bulk-edit-toggle-offset)));
width: calc(var(--bulk-edit-toggle-width) + var(--bulk-edit-toggle-offset));
background: var(--body-color);
border-top: solid 1px var(--secondary-border-color);
}

/* All checkbox */

& .form-checkbox.bulk-edit-checkbox.all {
Expand Down
2 changes: 1 addition & 1 deletion bookmarks/templates/bookmarks/bookmark_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@
{% endfor %}
</ul>

<div class="bookmark-pagination">
<div class="bookmark-pagination{% if request.user_profile.sticky_pagination %} sticky{% endif %}">
{% pagination bookmark_list.bookmarks_page %}
</div>
{% endif %}
23 changes: 23 additions & 0 deletions bookmarks/templates/settings/general.html
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,29 @@ <h2>Profile</h2>
Whether to open bookmarks a new page or in the same page.
</div>
</div>
<div class="form-group{% if form.items_per_page.errors %} has-error{% endif %}">
<label for="{{ form.items_per_page.id_for_label }}" class="form-label">Items per page</label>
{{ form.items_per_page|add_class:"form-input width-25 width-sm-100"|attr:"min:10" }}
{% if form.items_per_page.errors %}
<div class="form-input-hint is-error">
{{ form.items_per_page.errors }}
</div>
{% else %}
{% endif %}
<div class="form-input-hint">
The number of bookmarks to display per page.
</div>
</div>
<div class="form-group">
<label for="{{ form.sticky_pagination.id_for_label }}" class="form-checkbox">
{{ form.sticky_pagination }}
<i class="form-icon"></i> Sticky pagination
</label>
<div class="form-input-hint">
When enabled, the pagination controls will stick to the bottom of the screen, so that they are always
visible without having to scroll to the end of the page first.
</div>
</div>
<div class="form-group">
<label for="{{ form.tag_search.id_for_label }}" class="form-label">Tag search</label>
{{ form.tag_search|add_class:"form-select width-25 width-sm-100" }}
Expand Down
34 changes: 34 additions & 0 deletions bookmarks/tests/test_bookmarks_list_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -955,3 +955,37 @@ def test_empty_state(self):
self.assertInHTML(
'<p class="empty-title h5">You have no bookmarks yet</p>', html
)

def test_pagination_is_not_sticky_by_default(self):
self.setup_bookmark()
html = self.render_template()

self.assertIn('<div class="bookmark-pagination">', html)

def test_pagination_is_sticky_when_enabled_in_profile(self):
self.setup_bookmark()
profile = self.get_or_create_test_user().profile
profile.sticky_pagination = True
profile.save()
html = self.render_template()

self.assertIn('<div class="bookmark-pagination sticky">', html)

def test_items_per_page_is_30_by_default(self):
self.setup_numbered_bookmarks(50)
html = self.render_template()

soup = self.make_soup(html)
bookmarks = soup.select("li[ld-bookmark-item]")
self.assertEqual(30, len(bookmarks))

def test_items_per_page_is_configurable(self):
self.setup_numbered_bookmarks(50)
profile = self.get_or_create_test_user().profile
profile.items_per_page = 10
profile.save()
html = self.render_template()

soup = self.make_soup(html)
bookmarks = soup.select("li[ld-bookmark-item]")
self.assertEqual(10, len(bookmarks))
11 changes: 11 additions & 0 deletions bookmarks/tests/test_settings_general_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ def create_profile_form_data(self, overrides=None):
"permanent_notes": False,
"custom_css": "",
"auto_tagging_rules": "",
"items_per_page": "30",
"sticky_pagination": False,
}

return {**form_data, **overrides}
Expand Down Expand Up @@ -111,6 +113,8 @@ def test_update_profile(self):
"default_mark_unread": True,
"custom_css": "body { background-color: #000; }",
"auto_tagging_rules": "example.com tag",
"items_per_page": "10",
"sticky_pagination": True,
}
response = self.client.post(
reverse("bookmarks:settings.update"), form_data, follow=True
Expand Down Expand Up @@ -182,6 +186,13 @@ def test_update_profile(self):
self.assertEqual(
self.user.profile.auto_tagging_rules, form_data["auto_tagging_rules"]
)
self.assertEqual(
self.user.profile.items_per_page, int(form_data["items_per_page"])
)
self.assertEqual(
self.user.profile.sticky_pagination, form_data["sticky_pagination"]
)

self.assertSuccessMessage(html, "Profile updated")

def test_update_profile_should_not_be_called_without_respective_form_action(self):
Expand Down
2 changes: 0 additions & 2 deletions bookmarks/views/bookmarks.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@
from bookmarks.utils import get_safe_return_url
from bookmarks.views import contexts, partials, turbo

_default_page_size = 30


@login_required
def index(request):
Expand Down
3 changes: 1 addition & 2 deletions bookmarks/views/contexts.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
)
from bookmarks.services.wayback import generate_fallback_webarchive_url

DEFAULT_PAGE_SIZE = 30
CJK_RE = re.compile(r"[\u4e00-\u9fff]+")


Expand Down Expand Up @@ -181,7 +180,7 @@ def __init__(self, request: WSGIRequest) -> None:

query_set = request_context.get_bookmark_query_set(self.search)
page_number = request.GET.get("page")
paginator = Paginator(query_set, DEFAULT_PAGE_SIZE)
paginator = Paginator(query_set, user_profile.items_per_page)
bookmarks_page = paginator.get_page(page_number)
# Prefetch related objects, this avoids n+1 queries when accessing fields in templates
models.prefetch_related_objects(bookmarks_page.object_list, "owner", "tags")
Expand Down
21 changes: 17 additions & 4 deletions bookmarks/views/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@


@login_required
def general(request):
def general(request, status=200, context_overrides=None):
enable_refresh_favicons = django_settings.LD_ENABLE_REFRESH_FAVICONS
has_snapshot_support = django_settings.LD_ENABLE_SNAPSHOTS
success_message = _find_message_with_tag(
Expand All @@ -44,6 +44,9 @@ def general(request):
if request.user.is_superuser:
global_settings_form = GlobalSettingsForm(instance=GlobalSettings.get())

if context_overrides is None:
context_overrides = {}

return render(
request,
"settings/general.html",
Expand All @@ -55,16 +58,17 @@ def general(request):
"success_message": success_message,
"error_message": error_message,
"version_info": version_info,
**context_overrides,
},
status=status,
)


@login_required
def update(request):
if request.method == "POST":
if "update_profile" in request.POST:
update_profile(request)
messages.success(request, "Profile updated", "settings_success_message")
return update_profile(request)
if "update_global_settings" in request.POST:
update_global_settings(request)
messages.success(
Expand Down Expand Up @@ -101,13 +105,22 @@ def update_profile(request):
form = UserProfileForm(request.POST, instance=profile)
if form.is_valid():
form.save()
messages.success(request, "Profile updated", "settings_success_message")
# Load missing favicons if the feature was just enabled
if profile.enable_favicons and not favicons_were_enabled:
tasks.schedule_bookmarks_without_favicons(request.user)
# Load missing preview images if the feature was just enabled
if profile.enable_preview_images and not previews_were_enabled:
tasks.schedule_bookmarks_without_previews(request.user)
return form

return HttpResponseRedirect(reverse("bookmarks:settings.general"))

messages.error(
request,
"Profile update failed, check the form below for errors",
"settings_error_message",
)
return general(request, 422, {"form": form})


def update_global_settings(request):
Expand Down

0 comments on commit 450980a

Please sign in to comment.