mirror of
https://github.com/sissbruecker/linkding
synced 2024-11-25 04:40:20 +00:00
Add option for disabling tag grouping (#735)
* Configurable tag grouping * update tag group name --------- Co-authored-by: Sascha Ißbrücker <sascha.issbruecker@gmail.com>
This commit is contained in:
parent
a92a35cfb8
commit
e03f536925
6 changed files with 106 additions and 2 deletions
22
bookmarks/migrations/0035_userprofile_tag_grouping.py
Normal file
22
bookmarks/migrations/0035_userprofile_tag_grouping.py
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# Generated by Django 5.0.3 on 2024-05-14 08:28
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("bookmarks", "0034_bookmark_preview_image_file_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="userprofile",
|
||||||
|
name="tag_grouping",
|
||||||
|
field=models.CharField(
|
||||||
|
choices=[("alphabetical", "Alphabetical"), ("disabled", "Disabled")],
|
||||||
|
default="alphabetical",
|
||||||
|
max_length=12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -352,6 +352,12 @@ class UserProfile(models.Model):
|
||||||
(TAG_SEARCH_STRICT, "Strict"),
|
(TAG_SEARCH_STRICT, "Strict"),
|
||||||
(TAG_SEARCH_LAX, "Lax"),
|
(TAG_SEARCH_LAX, "Lax"),
|
||||||
]
|
]
|
||||||
|
TAG_GROUPING_ALPHABETICAL = "alphabetical"
|
||||||
|
TAG_GROUPING_DISABLED = "disabled"
|
||||||
|
TAG_GROUPING_CHOICES = [
|
||||||
|
(TAG_GROUPING_ALPHABETICAL, "Alphabetical"),
|
||||||
|
(TAG_GROUPING_DISABLED, "Disabled"),
|
||||||
|
]
|
||||||
user = models.OneToOneField(
|
user = models.OneToOneField(
|
||||||
get_user_model(), related_name="profile", on_delete=models.CASCADE
|
get_user_model(), related_name="profile", on_delete=models.CASCADE
|
||||||
)
|
)
|
||||||
|
@ -392,6 +398,12 @@ class UserProfile(models.Model):
|
||||||
blank=False,
|
blank=False,
|
||||||
default=TAG_SEARCH_STRICT,
|
default=TAG_SEARCH_STRICT,
|
||||||
)
|
)
|
||||||
|
tag_grouping = models.CharField(
|
||||||
|
max_length=12,
|
||||||
|
choices=TAG_GROUPING_CHOICES,
|
||||||
|
blank=False,
|
||||||
|
default=TAG_GROUPING_ALPHABETICAL,
|
||||||
|
)
|
||||||
enable_sharing = models.BooleanField(default=False, null=False)
|
enable_sharing = models.BooleanField(default=False, null=False)
|
||||||
enable_public_sharing = models.BooleanField(default=False, null=False)
|
enable_public_sharing = models.BooleanField(default=False, null=False)
|
||||||
enable_favicons = models.BooleanField(default=False, null=False)
|
enable_favicons = models.BooleanField(default=False, null=False)
|
||||||
|
@ -419,6 +431,7 @@ class UserProfileForm(forms.ModelForm):
|
||||||
"bookmark_link_target",
|
"bookmark_link_target",
|
||||||
"web_archive_integration",
|
"web_archive_integration",
|
||||||
"tag_search",
|
"tag_search",
|
||||||
|
"tag_grouping",
|
||||||
"enable_sharing",
|
"enable_sharing",
|
||||||
"enable_public_sharing",
|
"enable_public_sharing",
|
||||||
"enable_favicons",
|
"enable_favicons",
|
||||||
|
|
|
@ -110,6 +110,14 @@
|
||||||
result will also include bookmarks where a search term matches otherwise.
|
result will also include bookmarks where a search term matches otherwise.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="{{ form.tag_grouping.id_for_label }}" class="form-label">Tag grouping</label>
|
||||||
|
{{ form.tag_grouping|add_class:"form-select width-25 width-sm-100" }}
|
||||||
|
<div class="form-input-hint">
|
||||||
|
In alphabetical mode, tags will be grouped by the first letter.
|
||||||
|
If disabled, tags will not be grouped.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="{{ form.enable_favicons.id_for_label }}" class="form-checkbox">
|
<label for="{{ form.enable_favicons.id_for_label }}" class="form-checkbox">
|
||||||
{{ form.enable_favicons }}
|
{{ form.enable_favicons }}
|
||||||
|
|
|
@ -34,6 +34,7 @@ class SettingsGeneralViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
"enable_preview_images": False,
|
"enable_preview_images": False,
|
||||||
"enable_automatic_html_snapshots": True,
|
"enable_automatic_html_snapshots": True,
|
||||||
"tag_search": UserProfile.TAG_SEARCH_STRICT,
|
"tag_search": UserProfile.TAG_SEARCH_STRICT,
|
||||||
|
"tag_grouping": UserProfile.TAG_GROUPING_ALPHABETICAL,
|
||||||
"display_url": False,
|
"display_url": False,
|
||||||
"display_view_bookmark_action": True,
|
"display_view_bookmark_action": True,
|
||||||
"display_edit_bookmark_action": True,
|
"display_edit_bookmark_action": True,
|
||||||
|
@ -92,6 +93,7 @@ class SettingsGeneralViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
"enable_preview_images": True,
|
"enable_preview_images": True,
|
||||||
"enable_automatic_html_snapshots": False,
|
"enable_automatic_html_snapshots": False,
|
||||||
"tag_search": UserProfile.TAG_SEARCH_LAX,
|
"tag_search": UserProfile.TAG_SEARCH_LAX,
|
||||||
|
"tag_grouping": UserProfile.TAG_GROUPING_DISABLED,
|
||||||
"display_url": True,
|
"display_url": True,
|
||||||
"display_view_bookmark_action": False,
|
"display_view_bookmark_action": False,
|
||||||
"display_edit_bookmark_action": False,
|
"display_edit_bookmark_action": False,
|
||||||
|
@ -141,6 +143,7 @@ class SettingsGeneralViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
form_data["enable_automatic_html_snapshots"],
|
form_data["enable_automatic_html_snapshots"],
|
||||||
)
|
)
|
||||||
self.assertEqual(self.user.profile.tag_search, form_data["tag_search"])
|
self.assertEqual(self.user.profile.tag_search, form_data["tag_search"])
|
||||||
|
self.assertEqual(self.user.profile.tag_grouping, form_data["tag_grouping"])
|
||||||
self.assertEqual(self.user.profile.display_url, form_data["display_url"])
|
self.assertEqual(self.user.profile.display_url, form_data["display_url"])
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.user.profile.display_view_bookmark_action,
|
self.user.profile.display_view_bookmark_action,
|
||||||
|
|
|
@ -140,6 +140,43 @@ class TagCloudTemplateTest(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_group_when_grouping_disabled(self):
|
||||||
|
profile = self.get_or_create_test_user().profile
|
||||||
|
profile.tag_grouping = UserProfile.TAG_GROUPING_DISABLED
|
||||||
|
profile.save()
|
||||||
|
|
||||||
|
tags = [
|
||||||
|
self.setup_tag(name="Cockatoo"),
|
||||||
|
self.setup_tag(name="Badger"),
|
||||||
|
self.setup_tag(name="Buffalo"),
|
||||||
|
self.setup_tag(name="Chihuahua"),
|
||||||
|
self.setup_tag(name="Alpaca"),
|
||||||
|
self.setup_tag(name="Coyote"),
|
||||||
|
self.setup_tag(name="Aardvark"),
|
||||||
|
self.setup_tag(name="Bumblebee"),
|
||||||
|
self.setup_tag(name="Armadillo"),
|
||||||
|
]
|
||||||
|
self.setup_bookmark(tags=tags)
|
||||||
|
|
||||||
|
rendered_template = self.render_template()
|
||||||
|
|
||||||
|
self.assertTagGroups(
|
||||||
|
rendered_template,
|
||||||
|
[
|
||||||
|
[
|
||||||
|
"Aardvark",
|
||||||
|
"Alpaca",
|
||||||
|
"Armadillo",
|
||||||
|
"Badger",
|
||||||
|
"Buffalo",
|
||||||
|
"Bumblebee",
|
||||||
|
"Chihuahua",
|
||||||
|
"Cockatoo",
|
||||||
|
"Coyote",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
def test_no_duplicate_tag_names(self):
|
def test_no_duplicate_tag_names(self):
|
||||||
tags = [
|
tags = [
|
||||||
self.setup_tag(name="shared", user=self.setup_user(enable_sharing=True)),
|
self.setup_tag(name="shared", user=self.setup_user(enable_sharing=True)),
|
||||||
|
|
|
@ -264,7 +264,16 @@ class TagGroup:
|
||||||
return f"<{self.char} TagGroup>"
|
return f"<{self.char} TagGroup>"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_tag_groups(tags: Set[Tag]):
|
def create_tag_groups(mode: str, tags: Set[Tag]):
|
||||||
|
if mode == UserProfile.TAG_GROUPING_ALPHABETICAL:
|
||||||
|
return TagGroup._create_tag_groups_alphabetical(tags)
|
||||||
|
elif mode == UserProfile.TAG_GROUPING_DISABLED:
|
||||||
|
return TagGroup._create_tag_groups_disabled(tags)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"{mode} is not a valid tag grouping mode")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _create_tag_groups_alphabetical(tags: Set[Tag]):
|
||||||
# Ensure groups, as well as tags within groups, are ordered alphabetically
|
# Ensure groups, as well as tags within groups, are ordered alphabetically
|
||||||
sorted_tags = sorted(tags, key=lambda x: str.lower(x.name))
|
sorted_tags = sorted(tags, key=lambda x: str.lower(x.name))
|
||||||
group = None
|
group = None
|
||||||
|
@ -289,6 +298,18 @@ class TagGroup:
|
||||||
groups.append(cjk_group)
|
groups.append(cjk_group)
|
||||||
return groups
|
return groups
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _create_tag_groups_disabled(tags: Set[Tag]):
|
||||||
|
if len(tags) == 0:
|
||||||
|
return []
|
||||||
|
|
||||||
|
sorted_tags = sorted(tags, key=lambda x: str.lower(x.name))
|
||||||
|
group = TagGroup("Ungrouped")
|
||||||
|
for tag in sorted_tags:
|
||||||
|
group.tags.append(tag)
|
||||||
|
|
||||||
|
return [group]
|
||||||
|
|
||||||
|
|
||||||
class TagCloudContext:
|
class TagCloudContext:
|
||||||
request_context = RequestContext
|
request_context = RequestContext
|
||||||
|
@ -311,7 +332,7 @@ class TagCloudContext:
|
||||||
)
|
)
|
||||||
has_selected_tags = len(unique_selected_tags) > 0
|
has_selected_tags = len(unique_selected_tags) > 0
|
||||||
unselected_tags = set(unique_tags).symmetric_difference(unique_selected_tags)
|
unselected_tags = set(unique_tags).symmetric_difference(unique_selected_tags)
|
||||||
groups = TagGroup.create_tag_groups(unselected_tags)
|
groups = TagGroup.create_tag_groups(user_profile.tag_grouping, unselected_tags)
|
||||||
|
|
||||||
self.tags = unique_tags
|
self.tags = unique_tags
|
||||||
self.groups = groups
|
self.groups = groups
|
||||||
|
|
Loading…
Reference in a new issue