mirror of
https://github.com/sissbruecker/linkding
synced 2024-11-24 20:33:04 +00:00
Add configuration options for pagination (#835)
This commit is contained in:
parent
2aab2813f4
commit
450980a8d4
10 changed files with 157 additions and 10 deletions
|
@ -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),
|
||||
),
|
||||
]
|
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -450,6 +455,8 @@ class UserProfileForm(forms.ModelForm):
|
|||
"default_mark_unread",
|
||||
"custom_css",
|
||||
"auto_tagging_rules",
|
||||
"items_per_page",
|
||||
"sticky_pagination",
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 {
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -101,6 +101,29 @@
|
|||
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" }}
|
||||
|
|
|
@ -955,3 +955,37 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
|
|||
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))
|
||||
|
|
|
@ -43,6 +43,8 @@ class SettingsGeneralViewTestCase(TestCase, BookmarkFactoryMixin):
|
|||
"permanent_notes": False,
|
||||
"custom_css": "",
|
||||
"auto_tagging_rules": "",
|
||||
"items_per_page": "30",
|
||||
"sticky_pagination": False,
|
||||
}
|
||||
|
||||
return {**form_data, **overrides}
|
||||
|
@ -111,6 +113,8 @@ class SettingsGeneralViewTestCase(TestCase, BookmarkFactoryMixin):
|
|||
"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
|
||||
|
@ -182,6 +186,13 @@ class SettingsGeneralViewTestCase(TestCase, BookmarkFactoryMixin):
|
|||
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):
|
||||
|
|
|
@ -38,8 +38,6 @@ from bookmarks.services.bookmarks import (
|
|||
from bookmarks.utils import get_safe_return_url
|
||||
from bookmarks.views import contexts, partials, turbo
|
||||
|
||||
_default_page_size = 30
|
||||
|
||||
|
||||
@login_required
|
||||
def index(request):
|
||||
|
|
|
@ -21,7 +21,6 @@ from bookmarks.models import (
|
|||
)
|
||||
from bookmarks.services.wayback import generate_fallback_webarchive_url
|
||||
|
||||
DEFAULT_PAGE_SIZE = 30
|
||||
CJK_RE = re.compile(r"[\u4e00-\u9fff]+")
|
||||
|
||||
|
||||
|
@ -181,7 +180,7 @@ class BookmarkListContext:
|
|||
|
||||
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")
|
||||
|
|
|
@ -28,7 +28,7 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
|
||||
@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(
|
||||
|
@ -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",
|
||||
|
@ -55,7 +58,9 @@ def general(request):
|
|||
"success_message": success_message,
|
||||
"error_message": error_message,
|
||||
"version_info": version_info,
|
||||
**context_overrides,
|
||||
},
|
||||
status=status,
|
||||
)
|
||||
|
||||
|
||||
|
@ -63,8 +68,7 @@ def general(request):
|
|||
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(
|
||||
|
@ -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):
|
||||
|
|
Loading…
Reference in a new issue