diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..40c67a6 --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ +.PHONY: serve + +serve: + python manage.py runserver + +tasks: + python manage.py process_tasks + +test: + pytest + +format: + black bookmarks + black siteroot + npx prettier bookmarks/frontend --write diff --git a/README.md b/README.md index 7ee249f..830d6e5 100644 --- a/README.md +++ b/README.md @@ -281,6 +281,20 @@ python3 manage.py runserver ``` The frontend is now available under http://localhost:8000 +### Tests + +Run all tests with pytest: +``` +pytest +``` + +### Formatting + +Format Python code with black, and JavaScript code with prettier: +``` +make format +``` + ### DevContainers This repository also supports DevContainers: [![Open in Remote - Containers](https://img.shields.io/static/v1?label=Remote%20-%20Containers&message=Open&color=blue&logo=visualstudiocode)](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=git@github.com:sissbruecker/linkding.git) @@ -300,8 +314,3 @@ Start the Django development server with: python3 manage.py runserver ``` The frontend is now available under http://localhost:8000 - -Run all tests with pytest -``` -pytest -``` diff --git a/bookmarks/admin.py b/bookmarks/admin.py index 60efa93..b736b8b 100644 --- a/bookmarks/admin.py +++ b/bookmarks/admin.py @@ -14,80 +14,123 @@ from bookmarks.services.bookmarks import archive_bookmark, unarchive_bookmark class LinkdingAdminSite(AdminSite): - site_header = 'linkding administration' - site_title = 'linkding Admin' + site_header = "linkding administration" + site_title = "linkding Admin" class AdminBookmark(admin.ModelAdmin): - list_display = ('resolved_title', 'url', 'is_archived', 'owner', 'date_added') - search_fields = ('title', 'description', 'website_title', 'website_description', 'url', 'tags__name') - list_filter = ('owner__username', 'is_archived', 'unread', 'tags',) - ordering = ('-date_added',) - actions = ['delete_selected_bookmarks', 'archive_selected_bookmarks', 'unarchive_selected_bookmarks', 'mark_as_read', 'mark_as_unread'] + list_display = ("resolved_title", "url", "is_archived", "owner", "date_added") + search_fields = ( + "title", + "description", + "website_title", + "website_description", + "url", + "tags__name", + ) + list_filter = ( + "owner__username", + "is_archived", + "unread", + "tags", + ) + ordering = ("-date_added",) + actions = [ + "delete_selected_bookmarks", + "archive_selected_bookmarks", + "unarchive_selected_bookmarks", + "mark_as_read", + "mark_as_unread", + ] def get_actions(self, request): actions = super().get_actions(request) # Remove default delete action, which gets replaced by delete_selected_bookmarks below # The default action shows a confirmation page which can fail in production when selecting all bookmarks and the # number of objects to delete exceeds the value in DATA_UPLOAD_MAX_NUMBER_FIELDS (1000 by default) - del actions['delete_selected'] + del actions["delete_selected"] return actions def delete_selected_bookmarks(self, request, queryset: QuerySet): bookmarks_count = queryset.count() for bookmark in queryset: bookmark.delete() - self.message_user(request, ngettext( - '%d bookmark was successfully deleted.', - '%d bookmarks were successfully deleted.', - bookmarks_count, - ) % bookmarks_count, messages.SUCCESS) + self.message_user( + request, + ngettext( + "%d bookmark was successfully deleted.", + "%d bookmarks were successfully deleted.", + bookmarks_count, + ) + % bookmarks_count, + messages.SUCCESS, + ) def archive_selected_bookmarks(self, request, queryset: QuerySet): for bookmark in queryset: archive_bookmark(bookmark) bookmarks_count = queryset.count() - self.message_user(request, ngettext( - '%d bookmark was successfully archived.', - '%d bookmarks were successfully archived.', - bookmarks_count, - ) % bookmarks_count, messages.SUCCESS) + self.message_user( + request, + ngettext( + "%d bookmark was successfully archived.", + "%d bookmarks were successfully archived.", + bookmarks_count, + ) + % bookmarks_count, + messages.SUCCESS, + ) def unarchive_selected_bookmarks(self, request, queryset: QuerySet): for bookmark in queryset: unarchive_bookmark(bookmark) bookmarks_count = queryset.count() - self.message_user(request, ngettext( - '%d bookmark was successfully unarchived.', - '%d bookmarks were successfully unarchived.', - bookmarks_count, - ) % bookmarks_count, messages.SUCCESS) + self.message_user( + request, + ngettext( + "%d bookmark was successfully unarchived.", + "%d bookmarks were successfully unarchived.", + bookmarks_count, + ) + % bookmarks_count, + messages.SUCCESS, + ) def mark_as_read(self, request, queryset: QuerySet): bookmarks_count = queryset.count() queryset.update(unread=False) - self.message_user(request, ngettext( - '%d bookmark marked as read.', - '%d bookmarks marked as read.', - bookmarks_count, - ) % bookmarks_count, messages.SUCCESS) + self.message_user( + request, + ngettext( + "%d bookmark marked as read.", + "%d bookmarks marked as read.", + bookmarks_count, + ) + % bookmarks_count, + messages.SUCCESS, + ) def mark_as_unread(self, request, queryset: QuerySet): bookmarks_count = queryset.count() queryset.update(unread=True) - self.message_user(request, ngettext( - '%d bookmark marked as unread.', - '%d bookmarks marked as unread.', - bookmarks_count, - ) % bookmarks_count, messages.SUCCESS) + self.message_user( + request, + ngettext( + "%d bookmark marked as unread.", + "%d bookmarks marked as unread.", + bookmarks_count, + ) + % bookmarks_count, + messages.SUCCESS, + ) class AdminTag(admin.ModelAdmin): - list_display = ('name', 'bookmarks_count', 'owner', 'date_added') - search_fields = ('name', 'owner__username') - list_filter = ('owner__username',) - ordering = ('-date_added',) - actions = ['delete_unused_tags'] + list_display = ("name", "bookmarks_count", "owner", "date_added") + search_fields = ("name", "owner__username") + list_filter = ("owner__username",) + ordering = ("-date_added",) + actions = ["delete_unused_tags"] def get_queryset(self, request): queryset = super().get_queryset(request) @@ -97,7 +140,7 @@ class AdminTag(admin.ModelAdmin): def bookmarks_count(self, obj): return obj.bookmarks_count - bookmarks_count.admin_order_field = 'bookmarks_count' + bookmarks_count.admin_order_field = "bookmarks_count" def delete_unused_tags(self, request, queryset: QuerySet): unused_tags = queryset.filter(bookmark__isnull=True) @@ -106,23 +149,33 @@ class AdminTag(admin.ModelAdmin): tag.delete() if unused_tags_count > 0: - self.message_user(request, ngettext( - '%d unused tag was successfully deleted.', - '%d unused tags were successfully deleted.', - unused_tags_count, - ) % unused_tags_count, messages.SUCCESS) + self.message_user( + request, + ngettext( + "%d unused tag was successfully deleted.", + "%d unused tags were successfully deleted.", + unused_tags_count, + ) + % unused_tags_count, + messages.SUCCESS, + ) else: - self.message_user(request, gettext( - 'There were no unused tags in the selection', - ), messages.SUCCESS) + self.message_user( + request, + gettext( + "There were no unused tags in the selection", + ), + messages.SUCCESS, + ) class AdminUserProfileInline(admin.StackedInline): model = UserProfile can_delete = False - verbose_name_plural = 'Profile' - fk_name = 'user' - readonly_fields = ('search_preferences', ) + verbose_name_plural = "Profile" + fk_name = "user" + readonly_fields = ("search_preferences",) + class AdminCustomUser(UserAdmin): inlines = (AdminUserProfileInline,) @@ -134,15 +187,15 @@ class AdminCustomUser(UserAdmin): class AdminToast(admin.ModelAdmin): - list_display = ('key', 'message', 'owner', 'acknowledged') - search_fields = ('key', 'message') - list_filter = ('owner__username',) + list_display = ("key", "message", "owner", "acknowledged") + search_fields = ("key", "message") + list_filter = ("owner__username",) class AdminFeedToken(admin.ModelAdmin): - list_display = ('key', 'user') - search_fields = ['key'] - list_filter = ('user__username',) + list_display = ("key", "user") + search_fields = ["key"] + list_filter = ("user__username",) linkding_admin_site = LinkdingAdminSite() diff --git a/bookmarks/api/routes.py b/bookmarks/api/routes.py index 2cdd66b..bda6471 100644 --- a/bookmarks/api/routes.py +++ b/bookmarks/api/routes.py @@ -5,18 +5,28 @@ from rest_framework.response import Response from rest_framework.routers import DefaultRouter from bookmarks import queries -from bookmarks.api.serializers import BookmarkSerializer, TagSerializer, UserProfileSerializer +from bookmarks.api.serializers import ( + BookmarkSerializer, + TagSerializer, + UserProfileSerializer, +) from bookmarks.models import Bookmark, BookmarkSearch, Tag, User -from bookmarks.services.bookmarks import archive_bookmark, unarchive_bookmark, website_loader +from bookmarks.services.bookmarks import ( + archive_bookmark, + unarchive_bookmark, + website_loader, +) from bookmarks.services.website_loader import WebsiteMetadata -class BookmarkViewSet(viewsets.GenericViewSet, - mixins.ListModelMixin, - mixins.RetrieveModelMixin, - mixins.CreateModelMixin, - mixins.UpdateModelMixin, - mixins.DestroyModelMixin): +class BookmarkViewSet( + viewsets.GenericViewSet, + mixins.ListModelMixin, + mixins.RetrieveModelMixin, + mixins.CreateModelMixin, + mixins.UpdateModelMixin, + mixins.DestroyModelMixin, +): serializer_class = BookmarkSerializer def get_permissions(self): @@ -24,7 +34,7 @@ class BookmarkViewSet(viewsets.GenericViewSet, # The shared action should still filter bookmarks so that # unauthenticated users only see bookmarks from users that have public # sharing explicitly enabled - if self.action == 'shared': + if self.action == "shared": return [AllowAny()] # Otherwise use default permissions which should require authentication @@ -33,7 +43,7 @@ class BookmarkViewSet(viewsets.GenericViewSet, def get_queryset(self): user = self.request.user # For list action, use query set that applies search and tag projections - if self.action == 'list': + if self.action == "list": search = BookmarkSearch.from_request(self.request.GET) return queries.query_bookmarks(user, user.profile, search) @@ -41,9 +51,9 @@ class BookmarkViewSet(viewsets.GenericViewSet, return Bookmark.objects.all().filter(owner=user) def get_serializer_context(self): - return {'user': self.request.user} + return {"user": self.request.user} - @action(methods=['get'], detail=False) + @action(methods=["get"], detail=False) def archived(self, request): user = request.user search = BookmarkSearch.from_request(request.GET) @@ -53,51 +63,59 @@ class BookmarkViewSet(viewsets.GenericViewSet, data = serializer(page, many=True).data return self.get_paginated_response(data) - @action(methods=['get'], detail=False) + @action(methods=["get"], detail=False) def shared(self, request): search = BookmarkSearch.from_request(request.GET) user = User.objects.filter(username=search.user).first() public_only = not request.user.is_authenticated - query_set = queries.query_shared_bookmarks(user, request.user_profile, search, public_only) + query_set = queries.query_shared_bookmarks( + user, request.user_profile, search, public_only + ) page = self.paginate_queryset(query_set) serializer = self.get_serializer_class() data = serializer(page, many=True).data return self.get_paginated_response(data) - @action(methods=['post'], detail=True) + @action(methods=["post"], detail=True) def archive(self, request, pk): bookmark = self.get_object() archive_bookmark(bookmark) return Response(status=status.HTTP_204_NO_CONTENT) - @action(methods=['post'], detail=True) + @action(methods=["post"], detail=True) def unarchive(self, request, pk): bookmark = self.get_object() unarchive_bookmark(bookmark) return Response(status=status.HTTP_204_NO_CONTENT) - @action(methods=['get'], detail=False) + @action(methods=["get"], detail=False) def check(self, request): - url = request.GET.get('url') + url = request.GET.get("url") bookmark = Bookmark.objects.filter(owner=request.user, url=url).first() - existing_bookmark_data = self.get_serializer(bookmark).data if bookmark else None + existing_bookmark_data = ( + self.get_serializer(bookmark).data if bookmark else None + ) # Either return metadata from existing bookmark, or scrape from URL if bookmark: - metadata = WebsiteMetadata(url, bookmark.website_title, bookmark.website_description) + metadata = WebsiteMetadata( + url, bookmark.website_title, bookmark.website_description + ) else: metadata = website_loader.load_website_metadata(url) - return Response({ - 'bookmark': existing_bookmark_data, - 'metadata': metadata.to_dict() - }, status=status.HTTP_200_OK) + return Response( + {"bookmark": existing_bookmark_data, "metadata": metadata.to_dict()}, + status=status.HTTP_200_OK, + ) -class TagViewSet(viewsets.GenericViewSet, - mixins.ListModelMixin, - mixins.RetrieveModelMixin, - mixins.CreateModelMixin): +class TagViewSet( + viewsets.GenericViewSet, + mixins.ListModelMixin, + mixins.RetrieveModelMixin, + mixins.CreateModelMixin, +): serializer_class = TagSerializer def get_queryset(self): @@ -105,16 +123,16 @@ class TagViewSet(viewsets.GenericViewSet, return Tag.objects.all().filter(owner=user) def get_serializer_context(self): - return {'user': self.request.user} + return {"user": self.request.user} class UserViewSet(viewsets.GenericViewSet): - @action(methods=['get'], detail=False) + @action(methods=["get"], detail=False) def profile(self, request): return Response(UserProfileSerializer(request.user.profile).data) router = DefaultRouter() -router.register(r'bookmarks', BookmarkViewSet, basename='bookmark') -router.register(r'tags', TagViewSet, basename='tag') -router.register(r'user', UserViewSet, basename='user') +router.register(r"bookmarks", BookmarkViewSet, basename="bookmark") +router.register(r"tags", TagViewSet, basename="tag") +router.register(r"user", UserViewSet, basename="user") diff --git a/bookmarks/api/serializers.py b/bookmarks/api/serializers.py index b1fcee8..6fd437a 100644 --- a/bookmarks/api/serializers.py +++ b/bookmarks/api/serializers.py @@ -14,7 +14,7 @@ class TagListField(serializers.ListField): class BookmarkListSerializer(ListSerializer): def to_representation(self, data): # Prefetch nested relations to avoid n+1 queries - prefetch_related_objects(data, 'tags') + prefetch_related_objects(data, "tags") return super().to_representation(data) @@ -23,32 +23,32 @@ class BookmarkSerializer(serializers.ModelSerializer): class Meta: model = Bookmark fields = [ - 'id', - 'url', - 'title', - 'description', - 'notes', - 'website_title', - 'website_description', - 'is_archived', - 'unread', - 'shared', - 'tag_names', - 'date_added', - 'date_modified' + "id", + "url", + "title", + "description", + "notes", + "website_title", + "website_description", + "is_archived", + "unread", + "shared", + "tag_names", + "date_added", + "date_modified", ] read_only_fields = [ - 'website_title', - 'website_description', - 'date_added', - 'date_modified' + "website_title", + "website_description", + "date_added", + "date_modified", ] list_serializer_class = BookmarkListSerializer # Override optional char fields to provide default value - title = serializers.CharField(required=False, allow_blank=True, default='') - description = serializers.CharField(required=False, allow_blank=True, default='') - notes = serializers.CharField(required=False, allow_blank=True, default='') + title = serializers.CharField(required=False, allow_blank=True, default="") + description = serializers.CharField(required=False, allow_blank=True, default="") + notes = serializers.CharField(required=False, allow_blank=True, default="") is_archived = serializers.BooleanField(required=False, default=False) unread = serializers.BooleanField(required=False, default=False) shared = serializers.BooleanField(required=False, default=False) @@ -57,38 +57,38 @@ class BookmarkSerializer(serializers.ModelSerializer): def create(self, validated_data): bookmark = Bookmark() - bookmark.url = validated_data['url'] - bookmark.title = validated_data['title'] - bookmark.description = validated_data['description'] - bookmark.notes = validated_data['notes'] - bookmark.is_archived = validated_data['is_archived'] - bookmark.unread = validated_data['unread'] - bookmark.shared = validated_data['shared'] - tag_string = build_tag_string(validated_data['tag_names']) - return create_bookmark(bookmark, tag_string, self.context['user']) + bookmark.url = validated_data["url"] + bookmark.title = validated_data["title"] + bookmark.description = validated_data["description"] + bookmark.notes = validated_data["notes"] + bookmark.is_archived = validated_data["is_archived"] + bookmark.unread = validated_data["unread"] + bookmark.shared = validated_data["shared"] + tag_string = build_tag_string(validated_data["tag_names"]) + return create_bookmark(bookmark, tag_string, self.context["user"]) def update(self, instance: Bookmark, validated_data): # Update fields if they were provided in the payload - for key in ['url', 'title', 'description', 'notes', 'unread', 'shared']: + for key in ["url", "title", "description", "notes", "unread", "shared"]: if key in validated_data: setattr(instance, key, validated_data[key]) # Use tag string from payload, or use bookmark's current tags as fallback tag_string = build_tag_string(instance.tag_names) - if 'tag_names' in validated_data: - tag_string = build_tag_string(validated_data['tag_names']) + if "tag_names" in validated_data: + tag_string = build_tag_string(validated_data["tag_names"]) - return update_bookmark(instance, tag_string, self.context['user']) + return update_bookmark(instance, tag_string, self.context["user"]) class TagSerializer(serializers.ModelSerializer): class Meta: model = Tag - fields = ['id', 'name', 'date_added'] - read_only_fields = ['date_added'] + fields = ["id", "name", "date_added"] + read_only_fields = ["date_added"] def create(self, validated_data): - return get_or_create_tag(validated_data['name'], self.context['user']) + return get_or_create_tag(validated_data["name"], self.context["user"]) class UserProfileSerializer(serializers.ModelSerializer): diff --git a/bookmarks/apps.py b/bookmarks/apps.py index 89f1c7c..b58c2d9 100644 --- a/bookmarks/apps.py +++ b/bookmarks/apps.py @@ -2,7 +2,7 @@ from django.apps import AppConfig class BookmarksConfig(AppConfig): - name = 'bookmarks' + name = "bookmarks" def ready(self): # Register signal handlers diff --git a/bookmarks/context_processors.py b/bookmarks/context_processors.py index 71a33c7..8e79cf7 100644 --- a/bookmarks/context_processors.py +++ b/bookmarks/context_processors.py @@ -5,28 +5,32 @@ from bookmarks import utils def toasts(request): user = request.user - toast_messages = Toast.objects.filter(owner=user, acknowledged=False) if user.is_authenticated else [] + toast_messages = ( + Toast.objects.filter(owner=user, acknowledged=False) + if user.is_authenticated + else [] + ) has_toasts = len(toast_messages) > 0 return { - 'has_toasts': has_toasts, - 'toast_messages': toast_messages, + "has_toasts": has_toasts, + "toast_messages": toast_messages, } def public_shares(request): # Only check for public shares for anonymous users if not request.user.is_authenticated: - query_set = queries.query_shared_bookmarks(None, request.user_profile, BookmarkSearch(), True) + query_set = queries.query_shared_bookmarks( + None, request.user_profile, BookmarkSearch(), True + ) has_public_shares = query_set.count() > 0 return { - 'has_public_shares': has_public_shares, + "has_public_shares": has_public_shares, } return {} def app_version(request): - return { - 'app_version': utils.app_version - } + return {"app_version": utils.app_version} diff --git a/bookmarks/e2e/e2e_test_bookmark_form.py b/bookmarks/e2e/e2e_test_bookmark_form.py index a05639c..c4da421 100644 --- a/bookmarks/e2e/e2e_test_bookmark_form.py +++ b/bookmarks/e2e/e2e_test_bookmark_form.py @@ -6,38 +6,54 @@ from bookmarks.e2e.helpers import LinkdingE2ETestCase class BookmarkFormE2ETestCase(LinkdingE2ETestCase): def test_create_should_check_for_existing_bookmark(self): - existing_bookmark = self.setup_bookmark(title='Existing title', - description='Existing description', - notes='Existing notes', - tags=[self.setup_tag(name='tag1'), self.setup_tag(name='tag2')], - website_title='Existing website title', - website_description='Existing website description', - unread=True) - tag_names = ' '.join(existing_bookmark.tag_names) + existing_bookmark = self.setup_bookmark( + title="Existing title", + description="Existing description", + notes="Existing notes", + tags=[self.setup_tag(name="tag1"), self.setup_tag(name="tag2")], + website_title="Existing website title", + website_description="Existing website description", + unread=True, + ) + tag_names = " ".join(existing_bookmark.tag_names) with sync_playwright() as p: browser = self.setup_browser(p) page = browser.new_page() - page.goto(self.live_server_url + reverse('bookmarks:new')) + page.goto(self.live_server_url + reverse("bookmarks:new")) # Enter bookmarked URL - page.get_by_label('URL').fill(existing_bookmark.url) + page.get_by_label("URL").fill(existing_bookmark.url) # Already bookmarked hint should be visible - page.get_by_text('This URL is already bookmarked.').wait_for(timeout=2000) + page.get_by_text("This URL is already bookmarked.").wait_for(timeout=2000) # Form should be pre-filled with data from existing bookmark - self.assertEqual(existing_bookmark.title, page.get_by_label('Title').input_value()) - self.assertEqual(existing_bookmark.description, page.get_by_label('Description').input_value()) - self.assertEqual(existing_bookmark.notes, page.get_by_label('Notes').input_value()) - self.assertEqual(existing_bookmark.website_title, page.get_by_label('Title').get_attribute('placeholder')) - self.assertEqual(existing_bookmark.website_description, - page.get_by_label('Description').get_attribute('placeholder')) - self.assertEqual(tag_names, page.get_by_label('Tags').input_value()) - self.assertTrue(tag_names, page.get_by_label('Mark as unread').is_checked()) + self.assertEqual( + existing_bookmark.title, page.get_by_label("Title").input_value() + ) + self.assertEqual( + existing_bookmark.description, + page.get_by_label("Description").input_value(), + ) + self.assertEqual( + existing_bookmark.notes, page.get_by_label("Notes").input_value() + ) + self.assertEqual( + existing_bookmark.website_title, + page.get_by_label("Title").get_attribute("placeholder"), + ) + self.assertEqual( + existing_bookmark.website_description, + page.get_by_label("Description").get_attribute("placeholder"), + ) + self.assertEqual(tag_names, page.get_by_label("Tags").input_value()) + self.assertTrue(tag_names, page.get_by_label("Mark as unread").is_checked()) # Enter non-bookmarked URL - page.get_by_label('URL').fill('https://example.com/unknown') + page.get_by_label("URL").fill("https://example.com/unknown") # Already bookmarked hint should be hidden - page.get_by_text('This URL is already bookmarked.').wait_for(state='hidden', timeout=2000) + page.get_by_text("This URL is already bookmarked.").wait_for( + state="hidden", timeout=2000 + ) browser.close() @@ -47,21 +63,25 @@ class BookmarkFormE2ETestCase(LinkdingE2ETestCase): with sync_playwright() as p: browser = self.setup_browser(p) page = browser.new_page() - page.goto(self.live_server_url + reverse('bookmarks:edit', args=[bookmark.id])) + page.goto( + self.live_server_url + reverse("bookmarks:edit", args=[bookmark.id]) + ) page.wait_for_timeout(timeout=1000) - page.get_by_text('This URL is already bookmarked.').wait_for(state='hidden') + page.get_by_text("This URL is already bookmarked.").wait_for(state="hidden") def test_enter_url_of_existing_bookmark_should_show_notes(self): - bookmark = self.setup_bookmark(notes='Existing notes', description='Existing description') + bookmark = self.setup_bookmark( + notes="Existing notes", description="Existing description" + ) with sync_playwright() as p: browser = self.setup_browser(p) page = browser.new_page() - page.goto(self.live_server_url + reverse('bookmarks:new')) + page.goto(self.live_server_url + reverse("bookmarks:new")) - details = page.locator('details.notes') - expect(details).not_to_have_attribute('open', value='') + details = page.locator("details.notes") + expect(details).not_to_have_attribute("open", value="") - page.get_by_label('URL').fill(bookmark.url) - expect(details).to_have_attribute('open', value='') + page.get_by_label("URL").fill(bookmark.url) + expect(details).to_have_attribute("open", value="") diff --git a/bookmarks/e2e/e2e_test_bookmark_item.py b/bookmarks/e2e/e2e_test_bookmark_item.py index 796535f..6d7ddf9 100644 --- a/bookmarks/e2e/e2e_test_bookmark_item.py +++ b/bookmarks/e2e/e2e_test_bookmark_item.py @@ -9,15 +9,15 @@ from bookmarks.e2e.helpers import LinkdingE2ETestCase class BookmarkItemE2ETestCase(LinkdingE2ETestCase): @skip("Fails in CI, needs investigation") def test_toggle_notes_should_show_hide_notes(self): - bookmark = self.setup_bookmark(notes='Test notes') + bookmark = self.setup_bookmark(notes="Test notes") with sync_playwright() as p: - page = self.open(reverse('bookmarks:index'), p) + page = self.open(reverse("bookmarks:index"), p) - notes = self.locate_bookmark(bookmark.title).locator('.notes') + notes = self.locate_bookmark(bookmark.title).locator(".notes") expect(notes).to_be_hidden() - toggle_notes = page.locator('li button.toggle-notes') + toggle_notes = page.locator("li button.toggle-notes") toggle_notes.click() expect(notes).to_be_visible() diff --git a/bookmarks/e2e/e2e_test_bookmark_page_bulk_edit.py b/bookmarks/e2e/e2e_test_bookmark_page_bulk_edit.py index d9ca896..d58bf7d 100644 --- a/bookmarks/e2e/e2e_test_bookmark_page_bulk_edit.py +++ b/bookmarks/e2e/e2e_test_bookmark_page_bulk_edit.py @@ -9,100 +9,180 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase): def setup_test_data(self): self.setup_numbered_bookmarks(50) self.setup_numbered_bookmarks(50, archived=True) - self.setup_numbered_bookmarks(50, prefix='foo') - self.setup_numbered_bookmarks(50, archived=True, prefix='foo') + self.setup_numbered_bookmarks(50, prefix="foo") + self.setup_numbered_bookmarks(50, archived=True, prefix="foo") - self.assertEqual(50, Bookmark.objects.filter(is_archived=False, title__startswith='Bookmark').count()) - self.assertEqual(50, Bookmark.objects.filter(is_archived=True, title__startswith='Archived Bookmark').count()) - self.assertEqual(50, Bookmark.objects.filter(is_archived=False, title__startswith='foo').count()) - self.assertEqual(50, Bookmark.objects.filter(is_archived=True, title__startswith='foo').count()) + self.assertEqual( + 50, + Bookmark.objects.filter( + is_archived=False, title__startswith="Bookmark" + ).count(), + ) + self.assertEqual( + 50, + Bookmark.objects.filter( + is_archived=True, title__startswith="Archived Bookmark" + ).count(), + ) + self.assertEqual( + 50, + Bookmark.objects.filter(is_archived=False, title__startswith="foo").count(), + ) + self.assertEqual( + 50, + Bookmark.objects.filter(is_archived=True, title__startswith="foo").count(), + ) def test_active_bookmarks_bulk_select_across(self): self.setup_test_data() with sync_playwright() as p: - self.open(reverse('bookmarks:index'), p) + self.open(reverse("bookmarks:index"), p) self.locate_bulk_edit_toggle().click() self.locate_bulk_edit_select_all().click() self.locate_bulk_edit_select_across().click() - self.select_bulk_action('Delete') - self.locate_bulk_edit_bar().get_by_text('Execute').click() - self.locate_bulk_edit_bar().get_by_text('Confirm').click() + self.select_bulk_action("Delete") + self.locate_bulk_edit_bar().get_by_text("Execute").click() + self.locate_bulk_edit_bar().get_by_text("Confirm").click() - self.assertEqual(0, Bookmark.objects.filter(is_archived=False, title__startswith='Bookmark').count()) - self.assertEqual(50, Bookmark.objects.filter(is_archived=True, title__startswith='Archived Bookmark').count()) - self.assertEqual(0, Bookmark.objects.filter(is_archived=False, title__startswith='foo').count()) - self.assertEqual(50, Bookmark.objects.filter(is_archived=True, title__startswith='foo').count()) + self.assertEqual( + 0, + Bookmark.objects.filter( + is_archived=False, title__startswith="Bookmark" + ).count(), + ) + self.assertEqual( + 50, + Bookmark.objects.filter( + is_archived=True, title__startswith="Archived Bookmark" + ).count(), + ) + self.assertEqual( + 0, + Bookmark.objects.filter(is_archived=False, title__startswith="foo").count(), + ) + self.assertEqual( + 50, + Bookmark.objects.filter(is_archived=True, title__startswith="foo").count(), + ) def test_archived_bookmarks_bulk_select_across(self): self.setup_test_data() with sync_playwright() as p: - self.open(reverse('bookmarks:archived'), p) + self.open(reverse("bookmarks:archived"), p) self.locate_bulk_edit_toggle().click() self.locate_bulk_edit_select_all().click() self.locate_bulk_edit_select_across().click() - self.select_bulk_action('Delete') - self.locate_bulk_edit_bar().get_by_text('Execute').click() - self.locate_bulk_edit_bar().get_by_text('Confirm').click() + self.select_bulk_action("Delete") + self.locate_bulk_edit_bar().get_by_text("Execute").click() + self.locate_bulk_edit_bar().get_by_text("Confirm").click() - self.assertEqual(50, Bookmark.objects.filter(is_archived=False, title__startswith='Bookmark').count()) - self.assertEqual(0, Bookmark.objects.filter(is_archived=True, title__startswith='Archived Bookmark').count()) - self.assertEqual(50, Bookmark.objects.filter(is_archived=False, title__startswith='foo').count()) - self.assertEqual(0, Bookmark.objects.filter(is_archived=True, title__startswith='foo').count()) + self.assertEqual( + 50, + Bookmark.objects.filter( + is_archived=False, title__startswith="Bookmark" + ).count(), + ) + self.assertEqual( + 0, + Bookmark.objects.filter( + is_archived=True, title__startswith="Archived Bookmark" + ).count(), + ) + self.assertEqual( + 50, + Bookmark.objects.filter(is_archived=False, title__startswith="foo").count(), + ) + self.assertEqual( + 0, + Bookmark.objects.filter(is_archived=True, title__startswith="foo").count(), + ) def test_active_bookmarks_bulk_select_across_respects_query(self): self.setup_test_data() with sync_playwright() as p: - self.open(reverse('bookmarks:index') + '?q=foo', p) + self.open(reverse("bookmarks:index") + "?q=foo", p) self.locate_bulk_edit_toggle().click() self.locate_bulk_edit_select_all().click() self.locate_bulk_edit_select_across().click() - self.select_bulk_action('Delete') - self.locate_bulk_edit_bar().get_by_text('Execute').click() - self.locate_bulk_edit_bar().get_by_text('Confirm').click() + self.select_bulk_action("Delete") + self.locate_bulk_edit_bar().get_by_text("Execute").click() + self.locate_bulk_edit_bar().get_by_text("Confirm").click() - self.assertEqual(50, Bookmark.objects.filter(is_archived=False, title__startswith='Bookmark').count()) - self.assertEqual(50, Bookmark.objects.filter(is_archived=True, title__startswith='Archived Bookmark').count()) - self.assertEqual(0, Bookmark.objects.filter(is_archived=False, title__startswith='foo').count()) - self.assertEqual(50, Bookmark.objects.filter(is_archived=True, title__startswith='foo').count()) + self.assertEqual( + 50, + Bookmark.objects.filter( + is_archived=False, title__startswith="Bookmark" + ).count(), + ) + self.assertEqual( + 50, + Bookmark.objects.filter( + is_archived=True, title__startswith="Archived Bookmark" + ).count(), + ) + self.assertEqual( + 0, + Bookmark.objects.filter(is_archived=False, title__startswith="foo").count(), + ) + self.assertEqual( + 50, + Bookmark.objects.filter(is_archived=True, title__startswith="foo").count(), + ) def test_archived_bookmarks_bulk_select_across_respects_query(self): self.setup_test_data() with sync_playwright() as p: - self.open(reverse('bookmarks:archived') + '?q=foo', p) + self.open(reverse("bookmarks:archived") + "?q=foo", p) self.locate_bulk_edit_toggle().click() self.locate_bulk_edit_select_all().click() self.locate_bulk_edit_select_across().click() - self.select_bulk_action('Delete') - self.locate_bulk_edit_bar().get_by_text('Execute').click() - self.locate_bulk_edit_bar().get_by_text('Confirm').click() + self.select_bulk_action("Delete") + self.locate_bulk_edit_bar().get_by_text("Execute").click() + self.locate_bulk_edit_bar().get_by_text("Confirm").click() - self.assertEqual(50, Bookmark.objects.filter(is_archived=False, title__startswith='Bookmark').count()) - self.assertEqual(50, Bookmark.objects.filter(is_archived=True, title__startswith='Archived Bookmark').count()) - self.assertEqual(50, Bookmark.objects.filter(is_archived=False, title__startswith='foo').count()) - self.assertEqual(0, Bookmark.objects.filter(is_archived=True, title__startswith='foo').count()) + self.assertEqual( + 50, + Bookmark.objects.filter( + is_archived=False, title__startswith="Bookmark" + ).count(), + ) + self.assertEqual( + 50, + Bookmark.objects.filter( + is_archived=True, title__startswith="Archived Bookmark" + ).count(), + ) + self.assertEqual( + 50, + Bookmark.objects.filter(is_archived=False, title__startswith="foo").count(), + ) + self.assertEqual( + 0, + Bookmark.objects.filter(is_archived=True, title__startswith="foo").count(), + ) def test_select_all_toggles_all_checkboxes(self): self.setup_numbered_bookmarks(5) with sync_playwright() as p: - url = reverse('bookmarks:index') + url = reverse("bookmarks:index") page = self.open(url, p) self.locate_bulk_edit_toggle().click() - checkboxes = page.locator('label[ld-bulk-edit-checkbox] input') + checkboxes = page.locator("label[ld-bulk-edit-checkbox] input") self.assertEqual(6, checkboxes.count()) for i in range(checkboxes.count()): expect(checkboxes.nth(i)).not_to_be_checked() @@ -121,7 +201,7 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase): self.setup_numbered_bookmarks(5) with sync_playwright() as p: - url = reverse('bookmarks:index') + url = reverse("bookmarks:index") self.open(url, p) self.locate_bulk_edit_toggle().click() @@ -138,7 +218,7 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase): self.setup_numbered_bookmarks(5) with sync_playwright() as p: - url = reverse('bookmarks:index') + url = reverse("bookmarks:index") self.open(url, p) self.locate_bulk_edit_toggle().click() @@ -160,7 +240,7 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase): self.setup_numbered_bookmarks(5) with sync_playwright() as p: - url = reverse('bookmarks:index') + url = reverse("bookmarks:index") self.open(url, p) self.locate_bulk_edit_toggle().click() @@ -171,18 +251,22 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase): expect(self.locate_bulk_edit_select_across()).to_be_checked() # Hide select across by toggling a single bookmark - self.locate_bookmark('Bookmark 1').locator('label[ld-bulk-edit-checkbox]').click() + self.locate_bookmark("Bookmark 1").locator( + "label[ld-bulk-edit-checkbox]" + ).click() expect(self.locate_bulk_edit_select_across()).not_to_be_visible() # Show select across again, verify it is unchecked - self.locate_bookmark('Bookmark 1').locator('label[ld-bulk-edit-checkbox]').click() + self.locate_bookmark("Bookmark 1").locator( + "label[ld-bulk-edit-checkbox]" + ).click() expect(self.locate_bulk_edit_select_across()).not_to_be_checked() def test_execute_resets_all_checkboxes(self): self.setup_numbered_bookmarks(100) with sync_playwright() as p: - url = reverse('bookmarks:index') + url = reverse("bookmarks:index") page = self.open(url, p) # Select all bookmarks, enable select across @@ -191,18 +275,18 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase): self.locate_bulk_edit_select_across().click() # Get reference for bookmark list - bookmark_list = page.locator('ul[ld-bookmark-list]') + bookmark_list = page.locator("ul[ld-bookmark-list]") # Execute bulk action - self.select_bulk_action('Mark as unread') - self.locate_bulk_edit_bar().get_by_text('Execute').click() - self.locate_bulk_edit_bar().get_by_text('Confirm').click() + self.select_bulk_action("Mark as unread") + self.locate_bulk_edit_bar().get_by_text("Execute").click() + self.locate_bulk_edit_bar().get_by_text("Confirm").click() # Wait until bookmark list is updated (old reference becomes invisible) expect(bookmark_list).not_to_be_visible() # Verify bulk edit checkboxes are reset - checkboxes = page.locator('label[ld-bulk-edit-checkbox] input') + checkboxes = page.locator("label[ld-bulk-edit-checkbox] input") self.assertEqual(31, checkboxes.count()) for i in range(checkboxes.count()): expect(checkboxes.nth(i)).not_to_be_checked() @@ -215,18 +299,22 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase): self.setup_numbered_bookmarks(100) with sync_playwright() as p: - url = reverse('bookmarks:index') + url = reverse("bookmarks:index") self.open(url, p) self.locate_bulk_edit_toggle().click() self.locate_bulk_edit_select_all().click() - expect(self.locate_bulk_edit_bar().get_by_text('All pages (100 bookmarks)')).to_be_visible() + expect( + self.locate_bulk_edit_bar().get_by_text("All pages (100 bookmarks)") + ).to_be_visible() - self.select_bulk_action('Delete') - self.locate_bulk_edit_bar().get_by_text('Execute').click() - self.locate_bulk_edit_bar().get_by_text('Confirm').click() + self.select_bulk_action("Delete") + self.locate_bulk_edit_bar().get_by_text("Execute").click() + self.locate_bulk_edit_bar().get_by_text("Confirm").click() self.locate_bulk_edit_select_all().click() - expect(self.locate_bulk_edit_bar().get_by_text('All pages (70 bookmarks)')).to_be_visible() + expect( + self.locate_bulk_edit_bar().get_by_text("All pages (70 bookmarks)") + ).to_be_visible() diff --git a/bookmarks/e2e/e2e_test_bookmark_page_partial_updates.py b/bookmarks/e2e/e2e_test_bookmark_page_partial_updates.py index f218bb2..2d77283 100644 --- a/bookmarks/e2e/e2e_test_bookmark_page_partial_updates.py +++ b/bookmarks/e2e/e2e_test_bookmark_page_partial_updates.py @@ -16,13 +16,15 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase): # verify correct data is loaded on update self.setup_numbered_bookmarks(3, with_tags=True) self.setup_numbered_bookmarks(3, with_tags=True, archived=True) - self.setup_numbered_bookmarks(3, - shared=True, - prefix="Joe's Bookmark", - user=self.setup_user(enable_sharing=True)) + self.setup_numbered_bookmarks( + 3, + shared=True, + prefix="Joe's Bookmark", + user=self.setup_user(enable_sharing=True), + ) def assertVisibleBookmarks(self, titles: List[str]): - bookmark_tags = self.page.locator('li[ld-bookmark-item]') + bookmark_tags = self.page.locator("li[ld-bookmark-item]") expect(bookmark_tags).to_have_count(len(titles)) for title in titles: @@ -30,7 +32,7 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase): expect(matching_tag).to_be_visible() def assertVisibleTags(self, titles: List[str]): - tag_tags = self.page.locator('.tag-cloud .unselected-tags a') + tag_tags = self.page.locator(".tag-cloud .unselected-tags a") expect(tag_tags).to_have_count(len(titles)) for title in titles: @@ -38,65 +40,67 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase): expect(matching_tag).to_be_visible() def test_partial_update_respects_query(self): - self.setup_numbered_bookmarks(5, prefix='foo') - self.setup_numbered_bookmarks(5, prefix='bar') + self.setup_numbered_bookmarks(5, prefix="foo") + self.setup_numbered_bookmarks(5, prefix="bar") with sync_playwright() as p: - url = reverse('bookmarks:index') + '?q=foo' + url = reverse("bookmarks:index") + "?q=foo" self.open(url, p) - self.assertVisibleBookmarks(['foo 1', 'foo 2', 'foo 3', 'foo 4', 'foo 5']) + self.assertVisibleBookmarks(["foo 1", "foo 2", "foo 3", "foo 4", "foo 5"]) - self.locate_bookmark('foo 2').get_by_text('Archive').click() - self.assertVisibleBookmarks(['foo 1', 'foo 3', 'foo 4', 'foo 5']) + self.locate_bookmark("foo 2").get_by_text("Archive").click() + self.assertVisibleBookmarks(["foo 1", "foo 3", "foo 4", "foo 5"]) def test_partial_update_respects_sort(self): - self.setup_numbered_bookmarks(5, prefix='foo') + self.setup_numbered_bookmarks(5, prefix="foo") with sync_playwright() as p: - url = reverse('bookmarks:index') + '?sort=title_asc' + url = reverse("bookmarks:index") + "?sort=title_asc" page = self.open(url, p) - first_item = page.locator('li[ld-bookmark-item]').first - expect(first_item).to_contain_text('foo 1') + first_item = page.locator("li[ld-bookmark-item]").first + expect(first_item).to_contain_text("foo 1") - first_item.get_by_text('Archive').click() + first_item.get_by_text("Archive").click() - first_item = page.locator('li[ld-bookmark-item]').first - expect(first_item).to_contain_text('foo 2') + first_item = page.locator("li[ld-bookmark-item]").first + expect(first_item).to_contain_text("foo 2") def test_partial_update_respects_page(self): # add a suffix, otherwise 'foo 1' also matches 'foo 10' - self.setup_numbered_bookmarks(50, prefix='foo', suffix='-') + self.setup_numbered_bookmarks(50, prefix="foo", suffix="-") with sync_playwright() as p: - url = reverse('bookmarks:index') + '?q=foo&page=2' + url = reverse("bookmarks:index") + "?q=foo&page=2" self.open(url, p) # with descending sort, page two has 'foo 1' to 'foo 20' - expected_titles = [f'foo {i}-' for i in range(1, 21)] + expected_titles = [f"foo {i}-" for i in range(1, 21)] self.assertVisibleBookmarks(expected_titles) - self.locate_bookmark('foo 20-').get_by_text('Archive').click() + self.locate_bookmark("foo 20-").get_by_text("Archive").click() - expected_titles = [f'foo {i}-' for i in range(1, 20)] + expected_titles = [f"foo {i}-" for i in range(1, 20)] self.assertVisibleBookmarks(expected_titles) def test_multiple_partial_updates(self): self.setup_numbered_bookmarks(5) with sync_playwright() as p: - url = reverse('bookmarks:index') + url = reverse("bookmarks:index") self.open(url, p) - self.locate_bookmark('Bookmark 1').get_by_text('Archive').click() - self.assertVisibleBookmarks(['Bookmark 2', 'Bookmark 3', 'Bookmark 4', 'Bookmark 5']) + self.locate_bookmark("Bookmark 1").get_by_text("Archive").click() + self.assertVisibleBookmarks( + ["Bookmark 2", "Bookmark 3", "Bookmark 4", "Bookmark 5"] + ) - self.locate_bookmark('Bookmark 2').get_by_text('Archive').click() - self.assertVisibleBookmarks(['Bookmark 3', 'Bookmark 4', 'Bookmark 5']) + self.locate_bookmark("Bookmark 2").get_by_text("Archive").click() + self.assertVisibleBookmarks(["Bookmark 3", "Bookmark 4", "Bookmark 5"]) - self.locate_bookmark('Bookmark 3').get_by_text('Archive').click() - self.assertVisibleBookmarks(['Bookmark 4', 'Bookmark 5']) + self.locate_bookmark("Bookmark 3").get_by_text("Archive").click() + self.assertVisibleBookmarks(["Bookmark 4", "Bookmark 5"]) self.assertReloads(0) @@ -104,185 +108,201 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase): self.setup_fixture() with sync_playwright() as p: - self.open(reverse('bookmarks:index'), p) + self.open(reverse("bookmarks:index"), p) - self.locate_bookmark('Bookmark 2').get_by_text('Archive').click() + self.locate_bookmark("Bookmark 2").get_by_text("Archive").click() - self.assertVisibleBookmarks(['Bookmark 1', 'Bookmark 3']) - self.assertVisibleTags(['Tag 1', 'Tag 3']) + self.assertVisibleBookmarks(["Bookmark 1", "Bookmark 3"]) + self.assertVisibleTags(["Tag 1", "Tag 3"]) self.assertReloads(0) def test_active_bookmarks_partial_update_on_delete(self): self.setup_fixture() with sync_playwright() as p: - self.open(reverse('bookmarks:index'), p) + self.open(reverse("bookmarks:index"), p) - self.locate_bookmark('Bookmark 2').get_by_text('Remove').click() - self.locate_bookmark('Bookmark 2').get_by_text('Confirm').click() + self.locate_bookmark("Bookmark 2").get_by_text("Remove").click() + self.locate_bookmark("Bookmark 2").get_by_text("Confirm").click() - self.assertVisibleBookmarks(['Bookmark 1', 'Bookmark 3']) - self.assertVisibleTags(['Tag 1', 'Tag 3']) + self.assertVisibleBookmarks(["Bookmark 1", "Bookmark 3"]) + self.assertVisibleTags(["Tag 1", "Tag 3"]) self.assertReloads(0) def test_active_bookmarks_partial_update_on_mark_as_read(self): self.setup_fixture() - bookmark2 = self.get_numbered_bookmark('Bookmark 2') + bookmark2 = self.get_numbered_bookmark("Bookmark 2") bookmark2.unread = True bookmark2.save() with sync_playwright() as p: - self.open(reverse('bookmarks:index'), p) + self.open(reverse("bookmarks:index"), p) - expect(self.locate_bookmark('Bookmark 2')).to_have_class('unread') - self.locate_bookmark('Bookmark 2').get_by_text('Unread').click() - self.locate_bookmark('Bookmark 2').get_by_text('Yes').click() + expect(self.locate_bookmark("Bookmark 2")).to_have_class("unread") + self.locate_bookmark("Bookmark 2").get_by_text("Unread").click() + self.locate_bookmark("Bookmark 2").get_by_text("Yes").click() - expect(self.locate_bookmark('Bookmark 2')).not_to_have_class('unread') + expect(self.locate_bookmark("Bookmark 2")).not_to_have_class("unread") self.assertReloads(0) def test_active_bookmarks_partial_update_on_unshare(self): self.setup_fixture() - bookmark2 = self.get_numbered_bookmark('Bookmark 2') + bookmark2 = self.get_numbered_bookmark("Bookmark 2") bookmark2.shared = True bookmark2.save() with sync_playwright() as p: - self.open(reverse('bookmarks:index'), p) + self.open(reverse("bookmarks:index"), p) - expect(self.locate_bookmark('Bookmark 2')).to_have_class('shared') - self.locate_bookmark('Bookmark 2').get_by_text('Shared').click() - self.locate_bookmark('Bookmark 2').get_by_text('Yes').click() + expect(self.locate_bookmark("Bookmark 2")).to_have_class("shared") + self.locate_bookmark("Bookmark 2").get_by_text("Shared").click() + self.locate_bookmark("Bookmark 2").get_by_text("Yes").click() - expect(self.locate_bookmark('Bookmark 2')).not_to_have_class('shared') + expect(self.locate_bookmark("Bookmark 2")).not_to_have_class("shared") self.assertReloads(0) def test_active_bookmarks_partial_update_on_bulk_archive(self): self.setup_fixture() with sync_playwright() as p: - self.open(reverse('bookmarks:index'), p) + self.open(reverse("bookmarks:index"), p) self.locate_bulk_edit_toggle().click() - self.locate_bookmark('Bookmark 2').locator('label[ld-bulk-edit-checkbox]').click() - self.select_bulk_action('Archive') - self.locate_bulk_edit_bar().get_by_text('Execute').click() - self.locate_bulk_edit_bar().get_by_text('Confirm').click() + self.locate_bookmark("Bookmark 2").locator( + "label[ld-bulk-edit-checkbox]" + ).click() + self.select_bulk_action("Archive") + self.locate_bulk_edit_bar().get_by_text("Execute").click() + self.locate_bulk_edit_bar().get_by_text("Confirm").click() - self.assertVisibleBookmarks(['Bookmark 1', 'Bookmark 3']) - self.assertVisibleTags(['Tag 1', 'Tag 3']) + self.assertVisibleBookmarks(["Bookmark 1", "Bookmark 3"]) + self.assertVisibleTags(["Tag 1", "Tag 3"]) self.assertReloads(0) def test_active_bookmarks_partial_update_on_bulk_delete(self): self.setup_fixture() with sync_playwright() as p: - self.open(reverse('bookmarks:index'), p) + self.open(reverse("bookmarks:index"), p) self.locate_bulk_edit_toggle().click() - self.locate_bookmark('Bookmark 2').locator('label[ld-bulk-edit-checkbox]').click() - self.select_bulk_action('Delete') - self.locate_bulk_edit_bar().get_by_text('Execute').click() - self.locate_bulk_edit_bar().get_by_text('Confirm').click() + self.locate_bookmark("Bookmark 2").locator( + "label[ld-bulk-edit-checkbox]" + ).click() + self.select_bulk_action("Delete") + self.locate_bulk_edit_bar().get_by_text("Execute").click() + self.locate_bulk_edit_bar().get_by_text("Confirm").click() - self.assertVisibleBookmarks(['Bookmark 1', 'Bookmark 3']) - self.assertVisibleTags(['Tag 1', 'Tag 3']) + self.assertVisibleBookmarks(["Bookmark 1", "Bookmark 3"]) + self.assertVisibleTags(["Tag 1", "Tag 3"]) self.assertReloads(0) def test_archived_bookmarks_partial_update_on_unarchive(self): self.setup_fixture() with sync_playwright() as p: - self.open(reverse('bookmarks:archived'), p) + self.open(reverse("bookmarks:archived"), p) - self.locate_bookmark('Archived Bookmark 2').get_by_text('Unarchive').click() + self.locate_bookmark("Archived Bookmark 2").get_by_text("Unarchive").click() - self.assertVisibleBookmarks(['Archived Bookmark 1', 'Archived Bookmark 3']) - self.assertVisibleTags(['Archived Tag 1', 'Archived Tag 3']) + self.assertVisibleBookmarks(["Archived Bookmark 1", "Archived Bookmark 3"]) + self.assertVisibleTags(["Archived Tag 1", "Archived Tag 3"]) self.assertReloads(0) def test_archived_bookmarks_partial_update_on_delete(self): self.setup_fixture() with sync_playwright() as p: - self.open(reverse('bookmarks:archived'), p) + self.open(reverse("bookmarks:archived"), p) - self.locate_bookmark('Archived Bookmark 2').get_by_text('Remove').click() - self.locate_bookmark('Archived Bookmark 2').get_by_text('Confirm').click() + self.locate_bookmark("Archived Bookmark 2").get_by_text("Remove").click() + self.locate_bookmark("Archived Bookmark 2").get_by_text("Confirm").click() - self.assertVisibleBookmarks(['Archived Bookmark 1', 'Archived Bookmark 3']) - self.assertVisibleTags(['Archived Tag 1', 'Archived Tag 3']) + self.assertVisibleBookmarks(["Archived Bookmark 1", "Archived Bookmark 3"]) + self.assertVisibleTags(["Archived Tag 1", "Archived Tag 3"]) self.assertReloads(0) def test_archived_bookmarks_partial_update_on_bulk_unarchive(self): self.setup_fixture() with sync_playwright() as p: - self.open(reverse('bookmarks:archived'), p) + self.open(reverse("bookmarks:archived"), p) self.locate_bulk_edit_toggle().click() - self.locate_bookmark('Archived Bookmark 2').locator('label[ld-bulk-edit-checkbox]').click() - self.select_bulk_action('Unarchive') - self.locate_bulk_edit_bar().get_by_text('Execute').click() - self.locate_bulk_edit_bar().get_by_text('Confirm').click() + self.locate_bookmark("Archived Bookmark 2").locator( + "label[ld-bulk-edit-checkbox]" + ).click() + self.select_bulk_action("Unarchive") + self.locate_bulk_edit_bar().get_by_text("Execute").click() + self.locate_bulk_edit_bar().get_by_text("Confirm").click() - self.assertVisibleBookmarks(['Archived Bookmark 1', 'Archived Bookmark 3']) - self.assertVisibleTags(['Archived Tag 1', 'Archived Tag 3']) + self.assertVisibleBookmarks(["Archived Bookmark 1", "Archived Bookmark 3"]) + self.assertVisibleTags(["Archived Tag 1", "Archived Tag 3"]) self.assertReloads(0) def test_archived_bookmarks_partial_update_on_bulk_delete(self): self.setup_fixture() with sync_playwright() as p: - self.open(reverse('bookmarks:archived'), p) + self.open(reverse("bookmarks:archived"), p) self.locate_bulk_edit_toggle().click() - self.locate_bookmark('Archived Bookmark 2').locator('label[ld-bulk-edit-checkbox]').click() - self.select_bulk_action('Delete') - self.locate_bulk_edit_bar().get_by_text('Execute').click() - self.locate_bulk_edit_bar().get_by_text('Confirm').click() + self.locate_bookmark("Archived Bookmark 2").locator( + "label[ld-bulk-edit-checkbox]" + ).click() + self.select_bulk_action("Delete") + self.locate_bulk_edit_bar().get_by_text("Execute").click() + self.locate_bulk_edit_bar().get_by_text("Confirm").click() - self.assertVisibleBookmarks(['Archived Bookmark 1', 'Archived Bookmark 3']) - self.assertVisibleTags(['Archived Tag 1', 'Archived Tag 3']) + self.assertVisibleBookmarks(["Archived Bookmark 1", "Archived Bookmark 3"]) + self.assertVisibleTags(["Archived Tag 1", "Archived Tag 3"]) self.assertReloads(0) def test_shared_bookmarks_partial_update_on_unarchive(self): self.setup_fixture() - self.setup_numbered_bookmarks(3, shared=True, prefix="My Bookmark", with_tags=True) + self.setup_numbered_bookmarks( + 3, shared=True, prefix="My Bookmark", with_tags=True + ) with sync_playwright() as p: - self.open(reverse('bookmarks:shared'), p) + self.open(reverse("bookmarks:shared"), p) - self.locate_bookmark('My Bookmark 2').get_by_text('Archive').click() + self.locate_bookmark("My Bookmark 2").get_by_text("Archive").click() # Shared bookmarks page also shows archived bookmarks, though it probably shouldn't - self.assertVisibleBookmarks([ - 'My Bookmark 1', - 'My Bookmark 2', - 'My Bookmark 3', - "Joe's Bookmark 1", - "Joe's Bookmark 2", - "Joe's Bookmark 3", - ]) - self.assertVisibleTags(['Shared Tag 1', 'Shared Tag 2', 'Shared Tag 3']) + self.assertVisibleBookmarks( + [ + "My Bookmark 1", + "My Bookmark 2", + "My Bookmark 3", + "Joe's Bookmark 1", + "Joe's Bookmark 2", + "Joe's Bookmark 3", + ] + ) + self.assertVisibleTags(["Shared Tag 1", "Shared Tag 2", "Shared Tag 3"]) self.assertReloads(0) def test_shared_bookmarks_partial_update_on_delete(self): self.setup_fixture() - self.setup_numbered_bookmarks(3, shared=True, prefix="My Bookmark", with_tags=True) + self.setup_numbered_bookmarks( + 3, shared=True, prefix="My Bookmark", with_tags=True + ) with sync_playwright() as p: - self.open(reverse('bookmarks:shared'), p) + self.open(reverse("bookmarks:shared"), p) - self.locate_bookmark('My Bookmark 2').get_by_text('Remove').click() - self.locate_bookmark('My Bookmark 2').get_by_text('Confirm').click() + self.locate_bookmark("My Bookmark 2").get_by_text("Remove").click() + self.locate_bookmark("My Bookmark 2").get_by_text("Confirm").click() - self.assertVisibleBookmarks([ - 'My Bookmark 1', - 'My Bookmark 3', - "Joe's Bookmark 1", - "Joe's Bookmark 2", - "Joe's Bookmark 3", - ]) - self.assertVisibleTags(['Shared Tag 1', 'Shared Tag 3']) + self.assertVisibleBookmarks( + [ + "My Bookmark 1", + "My Bookmark 3", + "Joe's Bookmark 1", + "Joe's Bookmark 2", + "Joe's Bookmark 3", + ] + ) + self.assertVisibleTags(["Shared Tag 1", "Shared Tag 3"]) self.assertReloads(0) diff --git a/bookmarks/e2e/e2e_test_global_shortcuts.py b/bookmarks/e2e/e2e_test_global_shortcuts.py index 1d60b45..196d2aa 100644 --- a/bookmarks/e2e/e2e_test_global_shortcuts.py +++ b/bookmarks/e2e/e2e_test_global_shortcuts.py @@ -9,11 +9,11 @@ class GlobalShortcutsE2ETestCase(LinkdingE2ETestCase): with sync_playwright() as p: browser = self.setup_browser(p) page = browser.new_page() - page.goto(self.live_server_url + reverse('bookmarks:index')) + page.goto(self.live_server_url + reverse("bookmarks:index")) - page.press('body', 's') + page.press("body", "s") - expect(page.get_by_placeholder('Search for words or #tags')).to_be_focused() + expect(page.get_by_placeholder("Search for words or #tags")).to_be_focused() browser.close() @@ -21,10 +21,10 @@ class GlobalShortcutsE2ETestCase(LinkdingE2ETestCase): with sync_playwright() as p: browser = self.setup_browser(p) page = browser.new_page() - page.goto(self.live_server_url + reverse('bookmarks:index')) + page.goto(self.live_server_url + reverse("bookmarks:index")) - page.press('body', 'n') + page.press("body", "n") - expect(page).to_have_url(self.live_server_url + reverse('bookmarks:new')) + expect(page).to_have_url(self.live_server_url + reverse("bookmarks:new")) browser.close() diff --git a/bookmarks/e2e/e2e_test_settings_general.py b/bookmarks/e2e/e2e_test_settings_general.py index 9e761f1..cdbf192 100644 --- a/bookmarks/e2e/e2e_test_settings_general.py +++ b/bookmarks/e2e/e2e_test_settings_general.py @@ -9,12 +9,14 @@ class SettingsGeneralE2ETestCase(LinkdingE2ETestCase): with sync_playwright() as p: browser = self.setup_browser(p) page = browser.new_page() - page.goto(self.live_server_url + reverse('bookmarks:settings.general')) + page.goto(self.live_server_url + reverse("bookmarks:settings.general")) - enable_sharing = page.get_by_label('Enable bookmark sharing') - enable_sharing_label = page.get_by_text('Enable bookmark sharing') - enable_public_sharing = page.get_by_label('Enable public bookmark sharing') - enable_public_sharing_label = page.get_by_text('Enable public bookmark sharing') + enable_sharing = page.get_by_label("Enable bookmark sharing") + enable_sharing_label = page.get_by_text("Enable bookmark sharing") + enable_public_sharing = page.get_by_label("Enable public bookmark sharing") + enable_public_sharing_label = page.get_by_text( + "Enable public bookmark sharing" + ) # Public sharing is disabled by default expect(enable_sharing).not_to_be_checked() diff --git a/bookmarks/e2e/helpers.py b/bookmarks/e2e/helpers.py index f3931a2..a6cbcd5 100644 --- a/bookmarks/e2e/helpers.py +++ b/bookmarks/e2e/helpers.py @@ -7,24 +7,28 @@ from bookmarks.tests.helpers import BookmarkFactoryMixin class LinkdingE2ETestCase(LiveServerTestCase, BookmarkFactoryMixin): def setUp(self) -> None: self.client.force_login(self.get_or_create_test_user()) - self.cookie = self.client.cookies['sessionid'] + self.cookie = self.client.cookies["sessionid"] def setup_browser(self, playwright) -> BrowserContext: browser = playwright.chromium.launch(headless=True) context = browser.new_context() - context.add_cookies([{ - 'name': 'sessionid', - 'value': self.cookie.value, - 'domain': self.live_server_url.replace('http:', ''), - 'path': '/' - }]) + context.add_cookies( + [ + { + "name": "sessionid", + "value": self.cookie.value, + "domain": self.live_server_url.replace("http:", ""), + "path": "/", + } + ] + ) return context def open(self, url: str, playwright: Playwright) -> Page: browser = self.setup_browser(playwright) self.page = browser.new_page() self.page.goto(self.live_server_url + url) - self.page.on('load', self.on_load) + self.page.on("load", self.on_load) self.num_loads = 0 return self.page @@ -35,20 +39,24 @@ class LinkdingE2ETestCase(LiveServerTestCase, BookmarkFactoryMixin): self.assertEqual(self.num_loads, count) def locate_bookmark(self, title: str): - bookmark_tags = self.page.locator('li[ld-bookmark-item]') + bookmark_tags = self.page.locator("li[ld-bookmark-item]") return bookmark_tags.filter(has_text=title) def locate_bulk_edit_bar(self): - return self.page.locator('.bulk-edit-bar') + return self.page.locator(".bulk-edit-bar") def locate_bulk_edit_select_all(self): - return self.locate_bulk_edit_bar().locator('label[ld-bulk-edit-checkbox][all]') + return self.locate_bulk_edit_bar().locator("label[ld-bulk-edit-checkbox][all]") def locate_bulk_edit_select_across(self): - return self.locate_bulk_edit_bar().locator('label.select-across') + return self.locate_bulk_edit_bar().locator("label.select-across") def locate_bulk_edit_toggle(self): - return self.page.get_by_title('Bulk edit') + return self.page.get_by_title("Bulk edit") def select_bulk_action(self, value: str): - return self.locate_bulk_edit_bar().locator('select[name="bulk_action"]').select_option(value) + return ( + self.locate_bulk_edit_bar() + .locator('select[name="bulk_action"]') + .select_option(value) + ) diff --git a/bookmarks/feeds.py b/bookmarks/feeds.py index cec2129..7062818 100644 --- a/bookmarks/feeds.py +++ b/bookmarks/feeds.py @@ -17,17 +17,21 @@ class FeedContext: def sanitize(text: str): if not text: - return '' + return "" # remove control characters - valid_chars = ['\n', '\r', '\t'] - return ''.join(ch for ch in text if ch in valid_chars or unicodedata.category(ch)[0] != 'C') + valid_chars = ["\n", "\r", "\t"] + return "".join( + ch for ch in text if ch in valid_chars or unicodedata.category(ch)[0] != "C" + ) class BaseBookmarksFeed(Feed): def get_object(self, request, feed_key: str): feed_token = FeedToken.objects.get(key__exact=feed_key) - search = BookmarkSearch(q=request.GET.get('q', '')) - query_set = queries.query_bookmarks(feed_token.user, feed_token.user.profile, search) + search = BookmarkSearch(q=request.GET.get("q", "")) + query_set = queries.query_bookmarks( + feed_token.user, feed_token.user.profile, search + ) return FeedContext(feed_token, query_set) def item_title(self, item: Bookmark): @@ -44,22 +48,22 @@ class BaseBookmarksFeed(Feed): class AllBookmarksFeed(BaseBookmarksFeed): - title = 'All bookmarks' - description = 'All bookmarks' + title = "All bookmarks" + description = "All bookmarks" def link(self, context: FeedContext): - return reverse('bookmarks:feeds.all', args=[context.feed_token.key]) + return reverse("bookmarks:feeds.all", args=[context.feed_token.key]) def items(self, context: FeedContext): return context.query_set class UnreadBookmarksFeed(BaseBookmarksFeed): - title = 'Unread bookmarks' - description = 'All unread bookmarks' + title = "Unread bookmarks" + description = "All unread bookmarks" def link(self, context: FeedContext): - return reverse('bookmarks:feeds.unread', args=[context.feed_token.key]) + return reverse("bookmarks:feeds.unread", args=[context.feed_token.key]) def items(self, context: FeedContext): return context.query_set.filter(unread=True) diff --git a/bookmarks/management/commands/backup.py b/bookmarks/management/commands/backup.py index f5dd806..94b2d3f 100644 --- a/bookmarks/management/commands/backup.py +++ b/bookmarks/management/commands/backup.py @@ -8,19 +8,19 @@ class Command(BaseCommand): help = "Creates a backup of the linkding database" def add_arguments(self, parser): - parser.add_argument('destination', type=str, help='Backup file destination') + parser.add_argument("destination", type=str, help="Backup file destination") def handle(self, *args, **options): - destination = options['destination'] + destination = options["destination"] def progress(status, remaining, total): - self.stdout.write(f'Copied {total-remaining} of {total} pages...') + self.stdout.write(f"Copied {total-remaining} of {total} pages...") - source_db = sqlite3.connect(os.path.join('data', 'db.sqlite3')) + source_db = sqlite3.connect(os.path.join("data", "db.sqlite3")) backup_db = sqlite3.connect(destination) with backup_db: source_db.backup(backup_db, pages=50, progress=progress) backup_db.close() source_db.close() - self.stdout.write(self.style.SUCCESS(f'Backup created at {destination}')) + self.stdout.write(self.style.SUCCESS(f"Backup created at {destination}")) diff --git a/bookmarks/management/commands/create_initial_superuser.py b/bookmarks/management/commands/create_initial_superuser.py index 9f44a0f..42b6440 100644 --- a/bookmarks/management/commands/create_initial_superuser.py +++ b/bookmarks/management/commands/create_initial_superuser.py @@ -12,18 +12,20 @@ class Command(BaseCommand): def handle(self, *args, **options): User = get_user_model() - superuser_name = os.getenv('LD_SUPERUSER_NAME', None) - superuser_password = os.getenv('LD_SUPERUSER_PASSWORD', None) + superuser_name = os.getenv("LD_SUPERUSER_NAME", None) + superuser_password = os.getenv("LD_SUPERUSER_PASSWORD", None) # Skip if option is undefined if not superuser_name: - logger.info('Skip creating initial superuser, LD_SUPERUSER_NAME option is not defined') + logger.info( + "Skip creating initial superuser, LD_SUPERUSER_NAME option is not defined" + ) return # Skip if user already exists user_exists = User.objects.filter(username=superuser_name).exists() if user_exists: - logger.info('Skip creating initial superuser, user already exists') + logger.info("Skip creating initial superuser, user already exists") return user = User(username=superuser_name, is_superuser=True, is_staff=True) @@ -34,4 +36,4 @@ class Command(BaseCommand): user.set_unusable_password() user.save() - logger.info('Created initial superuser') + logger.info("Created initial superuser") diff --git a/bookmarks/management/commands/enable_wal.py b/bookmarks/management/commands/enable_wal.py index 6c66e3d..6e15c15 100644 --- a/bookmarks/management/commands/enable_wal.py +++ b/bookmarks/management/commands/enable_wal.py @@ -14,11 +14,11 @@ class Command(BaseCommand): if not settings.USE_SQLITE: return - connection = connections['default'] + connection = connections["default"] with connection.cursor() as cursor: cursor.execute("PRAGMA journal_mode") current_mode = cursor.fetchone()[0] - logger.info(f'Current journal mode: {current_mode}') - if current_mode != 'wal': + logger.info(f"Current journal mode: {current_mode}") + if current_mode != "wal": cursor.execute("PRAGMA journal_mode=wal;") - logger.info('Switched to WAL journal mode') + logger.info("Switched to WAL journal mode") diff --git a/bookmarks/management/commands/ensure_superuser.py b/bookmarks/management/commands/ensure_superuser.py index 06dbd2d..92656f8 100644 --- a/bookmarks/management/commands/ensure_superuser.py +++ b/bookmarks/management/commands/ensure_superuser.py @@ -6,13 +6,15 @@ class Command(BaseCommand): help = "Creates an admin user non-interactively if it doesn't exist" def add_arguments(self, parser): - parser.add_argument('--username', help="Admin's username") - parser.add_argument('--email', help="Admin's email") - parser.add_argument('--password', help="Admin's password") + parser.add_argument("--username", help="Admin's username") + parser.add_argument("--email", help="Admin's email") + parser.add_argument("--password", help="Admin's password") def handle(self, *args, **options): User = get_user_model() - if not User.objects.filter(username=options['username']).exists(): - User.objects.create_superuser(username=options['username'], - email=options['email'], - password=options['password']) + if not User.objects.filter(username=options["username"]).exists(): + User.objects.create_superuser( + username=options["username"], + email=options["email"], + password=options["password"], + ) diff --git a/bookmarks/management/commands/import_netscape.py b/bookmarks/management/commands/import_netscape.py index 5691584..e2fafdc 100644 --- a/bookmarks/management/commands/import_netscape.py +++ b/bookmarks/management/commands/import_netscape.py @@ -5,15 +5,17 @@ from bookmarks.services.importer import import_netscape_html class Command(BaseCommand): - help = 'Import Netscape HTML bookmark file' + help = "Import Netscape HTML bookmark file" def add_arguments(self, parser): - parser.add_argument('file', type=str, help='Path to file') - parser.add_argument('user', type=str, help='Name of the user for which to import') + parser.add_argument("file", type=str, help="Path to file") + parser.add_argument( + "user", type=str, help="Name of the user for which to import" + ) def handle(self, *args, **kwargs): - filepath = kwargs['file'] - username = kwargs['user'] + filepath = kwargs["file"] + username = kwargs["user"] with open(filepath) as html_file: html = html_file.read() user = User.objects.get(username=username) diff --git a/bookmarks/migrations/0001_initial.py b/bookmarks/migrations/0001_initial.py index a82f3f5..3c5dc85 100644 --- a/bookmarks/migrations/0001_initial.py +++ b/bookmarks/migrations/0001_initial.py @@ -15,19 +15,36 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name='Bookmark', + name="Bookmark", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('url', models.URLField()), - ('title', models.CharField(max_length=512)), - ('description', models.TextField()), - ('website_title', models.CharField(blank=True, max_length=512, null=True)), - ('website_description', models.TextField(blank=True, null=True)), - ('unread', models.BooleanField(default=True)), - ('date_added', models.DateTimeField()), - ('date_modified', models.DateTimeField()), - ('date_accessed', models.DateTimeField(blank=True, null=True)), - ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("url", models.URLField()), + ("title", models.CharField(max_length=512)), + ("description", models.TextField()), + ( + "website_title", + models.CharField(blank=True, max_length=512, null=True), + ), + ("website_description", models.TextField(blank=True, null=True)), + ("unread", models.BooleanField(default=True)), + ("date_added", models.DateTimeField()), + ("date_modified", models.DateTimeField()), + ("date_accessed", models.DateTimeField(blank=True, null=True)), + ( + "owner", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), ], ), ] diff --git a/bookmarks/migrations/0002_auto_20190629_2303.py b/bookmarks/migrations/0002_auto_20190629_2303.py index 9d9a2e6..c63a2f3 100644 --- a/bookmarks/migrations/0002_auto_20190629_2303.py +++ b/bookmarks/migrations/0002_auto_20190629_2303.py @@ -9,22 +9,36 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('bookmarks', '0001_initial'), + ("bookmarks", "0001_initial"), ] operations = [ migrations.CreateModel( - name='Tag', + name="Tag", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=64)), - ('date_added', models.DateTimeField()), - ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=64)), + ("date_added", models.DateTimeField()), + ( + "owner", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), ], ), migrations.AddField( - model_name='bookmark', - name='tags', - field=models.ManyToManyField(to='bookmarks.Tag'), + model_name="bookmark", + name="tags", + field=models.ManyToManyField(to="bookmarks.Tag"), ), ] diff --git a/bookmarks/migrations/0003_auto_20200913_0656.py b/bookmarks/migrations/0003_auto_20200913_0656.py index 4c3758e..78ae8c3 100644 --- a/bookmarks/migrations/0003_auto_20200913_0656.py +++ b/bookmarks/migrations/0003_auto_20200913_0656.py @@ -6,13 +6,13 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('bookmarks', '0002_auto_20190629_2303'), + ("bookmarks", "0002_auto_20190629_2303"), ] operations = [ migrations.AlterField( - model_name='bookmark', - name='url', + model_name="bookmark", + name="url", field=models.URLField(max_length=2048), ), ] diff --git a/bookmarks/migrations/0004_auto_20200926_1028.py b/bookmarks/migrations/0004_auto_20200926_1028.py index 1636b0c..9b2f196 100644 --- a/bookmarks/migrations/0004_auto_20200926_1028.py +++ b/bookmarks/migrations/0004_auto_20200926_1028.py @@ -6,18 +6,18 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('bookmarks', '0003_auto_20200913_0656'), + ("bookmarks", "0003_auto_20200913_0656"), ] operations = [ migrations.AlterField( - model_name='bookmark', - name='description', + model_name="bookmark", + name="description", field=models.TextField(blank=True), ), migrations.AlterField( - model_name='bookmark', - name='title', + model_name="bookmark", + name="title", field=models.CharField(blank=True, max_length=512), ), ] diff --git a/bookmarks/migrations/0005_auto_20210103_1212.py b/bookmarks/migrations/0005_auto_20210103_1212.py index 11e5a63..d0d314a 100644 --- a/bookmarks/migrations/0005_auto_20210103_1212.py +++ b/bookmarks/migrations/0005_auto_20210103_1212.py @@ -7,13 +7,16 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('bookmarks', '0004_auto_20200926_1028'), + ("bookmarks", "0004_auto_20200926_1028"), ] operations = [ migrations.AlterField( - model_name='bookmark', - name='url', - field=models.CharField(max_length=2048, validators=[bookmarks.validators.BookmarkURLValidator()]), + model_name="bookmark", + name="url", + field=models.CharField( + max_length=2048, + validators=[bookmarks.validators.BookmarkURLValidator()], + ), ), ] diff --git a/bookmarks/migrations/0006_bookmark_is_archived.py b/bookmarks/migrations/0006_bookmark_is_archived.py index 21190e9..ab713f7 100644 --- a/bookmarks/migrations/0006_bookmark_is_archived.py +++ b/bookmarks/migrations/0006_bookmark_is_archived.py @@ -6,13 +6,13 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('bookmarks', '0005_auto_20210103_1212'), + ("bookmarks", "0005_auto_20210103_1212"), ] operations = [ migrations.AddField( - model_name='bookmark', - name='is_archived', + model_name="bookmark", + name="is_archived", field=models.BooleanField(default=False), ), ] diff --git a/bookmarks/migrations/0007_userprofile.py b/bookmarks/migrations/0007_userprofile.py index 20850b5..c9b229c 100644 --- a/bookmarks/migrations/0007_userprofile.py +++ b/bookmarks/migrations/0007_userprofile.py @@ -6,8 +6,8 @@ import django.db.models.deletion def forwards(apps, schema_editor): - User = apps.get_model('auth', 'User') - UserProfile = apps.get_model('bookmarks', 'UserProfile') + User = apps.get_model("auth", "User") + UserProfile = apps.get_model("bookmarks", "UserProfile") for user in User.objects.all(): try: if user.profile: @@ -24,19 +24,42 @@ def reverse(apps, schema_editor): class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('bookmarks', '0006_bookmark_is_archived'), + ("bookmarks", "0006_bookmark_is_archived"), ] operations = [ migrations.CreateModel( - name='UserProfile', + name="UserProfile", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('theme', - models.CharField(choices=[('auto', 'Auto'), ('light', 'Light'), ('dark', 'Dark')], default='auto', - max_length=10)), - ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profile', - to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "theme", + models.CharField( + choices=[ + ("auto", "Auto"), + ("light", "Light"), + ("dark", "Dark"), + ], + default="auto", + max_length=10, + ), + ), + ( + "user", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + related_name="profile", + to=settings.AUTH_USER_MODEL, + ), + ), ], ), migrations.RunPython(forwards, reverse), diff --git a/bookmarks/migrations/0008_userprofile_bookmark_date_display.py b/bookmarks/migrations/0008_userprofile_bookmark_date_display.py index f27ce49..41dce4f 100644 --- a/bookmarks/migrations/0008_userprofile_bookmark_date_display.py +++ b/bookmarks/migrations/0008_userprofile_bookmark_date_display.py @@ -6,13 +6,21 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('bookmarks', '0007_userprofile'), + ("bookmarks", "0007_userprofile"), ] operations = [ migrations.AddField( - model_name='userprofile', - name='bookmark_date_display', - field=models.CharField(choices=[('relative', 'Relative'), ('absolute', 'Absolute'), ('hidden', 'Hidden')], default='relative', max_length=10), + model_name="userprofile", + name="bookmark_date_display", + field=models.CharField( + choices=[ + ("relative", "Relative"), + ("absolute", "Absolute"), + ("hidden", "Hidden"), + ], + default="relative", + max_length=10, + ), ), ] diff --git a/bookmarks/migrations/0009_bookmark_web_archive_snapshot_url.py b/bookmarks/migrations/0009_bookmark_web_archive_snapshot_url.py index 89483d1..0f35c64 100644 --- a/bookmarks/migrations/0009_bookmark_web_archive_snapshot_url.py +++ b/bookmarks/migrations/0009_bookmark_web_archive_snapshot_url.py @@ -6,13 +6,13 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('bookmarks', '0008_userprofile_bookmark_date_display'), + ("bookmarks", "0008_userprofile_bookmark_date_display"), ] operations = [ migrations.AddField( - model_name='bookmark', - name='web_archive_snapshot_url', + model_name="bookmark", + name="web_archive_snapshot_url", field=models.CharField(blank=True, max_length=2048), ), ] diff --git a/bookmarks/migrations/0010_userprofile_bookmark_link_target.py b/bookmarks/migrations/0010_userprofile_bookmark_link_target.py index 90883d1..3c4e2e7 100644 --- a/bookmarks/migrations/0010_userprofile_bookmark_link_target.py +++ b/bookmarks/migrations/0010_userprofile_bookmark_link_target.py @@ -6,13 +6,17 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('bookmarks', '0009_bookmark_web_archive_snapshot_url'), + ("bookmarks", "0009_bookmark_web_archive_snapshot_url"), ] operations = [ migrations.AddField( - model_name='userprofile', - name='bookmark_link_target', - field=models.CharField(choices=[('_blank', 'New page'), ('_self', 'Same page')], default='_blank', max_length=10), + model_name="userprofile", + name="bookmark_link_target", + field=models.CharField( + choices=[("_blank", "New page"), ("_self", "Same page")], + default="_blank", + max_length=10, + ), ), ] diff --git a/bookmarks/migrations/0011_userprofile_web_archive_integration.py b/bookmarks/migrations/0011_userprofile_web_archive_integration.py index 309e4b0..dbc6c58 100644 --- a/bookmarks/migrations/0011_userprofile_web_archive_integration.py +++ b/bookmarks/migrations/0011_userprofile_web_archive_integration.py @@ -6,13 +6,17 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('bookmarks', '0010_userprofile_bookmark_link_target'), + ("bookmarks", "0010_userprofile_bookmark_link_target"), ] operations = [ migrations.AddField( - model_name='userprofile', - name='web_archive_integration', - field=models.CharField(choices=[('disabled', 'Disabled'), ('enabled', 'Enabled')], default='disabled', max_length=10), + model_name="userprofile", + name="web_archive_integration", + field=models.CharField( + choices=[("disabled", "Disabled"), ("enabled", "Enabled")], + default="disabled", + max_length=10, + ), ), ] diff --git a/bookmarks/migrations/0012_toast.py b/bookmarks/migrations/0012_toast.py index b4136c0..0fb0569 100644 --- a/bookmarks/migrations/0012_toast.py +++ b/bookmarks/migrations/0012_toast.py @@ -9,18 +9,32 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('bookmarks', '0011_userprofile_web_archive_integration'), + ("bookmarks", "0011_userprofile_web_archive_integration"), ] operations = [ migrations.CreateModel( - name='Toast', + name="Toast", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('key', models.CharField(max_length=50)), - ('message', models.TextField()), - ('acknowledged', models.BooleanField(default=False)), - ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("key", models.CharField(max_length=50)), + ("message", models.TextField()), + ("acknowledged", models.BooleanField(default=False)), + ( + "owner", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), ], ), ] diff --git a/bookmarks/migrations/0013_web_archive_optin_toast.py b/bookmarks/migrations/0013_web_archive_optin_toast.py index c079a19..93627b9 100644 --- a/bookmarks/migrations/0013_web_archive_optin_toast.py +++ b/bookmarks/migrations/0013_web_archive_optin_toast.py @@ -10,19 +10,21 @@ User = get_user_model() def forwards(apps, schema_editor): for user in User.objects.all(): - toast = Toast(key='web_archive_opt_in_hint', - message='The Internet Archive Wayback Machine integration has been disabled by default. Check the Settings to re-enable it.', - owner=user) + toast = Toast( + key="web_archive_opt_in_hint", + message="The Internet Archive Wayback Machine integration has been disabled by default. Check the Settings to re-enable it.", + owner=user, + ) toast.save() def reverse(apps, schema_editor): - Toast.objects.filter(key='web_archive_opt_in_hint').delete() + Toast.objects.filter(key="web_archive_opt_in_hint").delete() class Migration(migrations.Migration): dependencies = [ - ('bookmarks', '0012_toast'), + ("bookmarks", "0012_toast"), ] operations = [ diff --git a/bookmarks/migrations/0014_alter_bookmark_unread.py b/bookmarks/migrations/0014_alter_bookmark_unread.py index 885b1b5..8e88021 100644 --- a/bookmarks/migrations/0014_alter_bookmark_unread.py +++ b/bookmarks/migrations/0014_alter_bookmark_unread.py @@ -4,7 +4,7 @@ from django.db import migrations, models def forwards(apps, schema_editor): - Bookmark = apps.get_model('bookmarks', 'Bookmark') + Bookmark = apps.get_model("bookmarks", "Bookmark") Bookmark.objects.update(unread=False) @@ -14,13 +14,13 @@ def reverse(apps, schema_editor): class Migration(migrations.Migration): dependencies = [ - ('bookmarks', '0013_web_archive_optin_toast'), + ("bookmarks", "0013_web_archive_optin_toast"), ] operations = [ migrations.AlterField( - model_name='bookmark', - name='unread', + model_name="bookmark", + name="unread", field=models.BooleanField(default=False), ), migrations.RunPython(forwards, reverse), diff --git a/bookmarks/migrations/0015_feedtoken.py b/bookmarks/migrations/0015_feedtoken.py index 15b4e0f..00d155d 100644 --- a/bookmarks/migrations/0015_feedtoken.py +++ b/bookmarks/migrations/0015_feedtoken.py @@ -9,16 +9,26 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('bookmarks', '0014_alter_bookmark_unread'), + ("bookmarks", "0014_alter_bookmark_unread"), ] operations = [ migrations.CreateModel( - name='FeedToken', + name="FeedToken", fields=[ - ('key', models.CharField(max_length=40, primary_key=True, serialize=False)), - ('created', models.DateTimeField(auto_now_add=True)), - ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='feed_token', to=settings.AUTH_USER_MODEL)), + ( + "key", + models.CharField(max_length=40, primary_key=True, serialize=False), + ), + ("created", models.DateTimeField(auto_now_add=True)), + ( + "user", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + related_name="feed_token", + to=settings.AUTH_USER_MODEL, + ), + ), ], ), ] diff --git a/bookmarks/migrations/0016_bookmark_shared.py b/bookmarks/migrations/0016_bookmark_shared.py index a780b44..c05ee32 100644 --- a/bookmarks/migrations/0016_bookmark_shared.py +++ b/bookmarks/migrations/0016_bookmark_shared.py @@ -6,13 +6,13 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('bookmarks', '0015_feedtoken'), + ("bookmarks", "0015_feedtoken"), ] operations = [ migrations.AddField( - model_name='bookmark', - name='shared', + model_name="bookmark", + name="shared", field=models.BooleanField(default=False), ), ] diff --git a/bookmarks/migrations/0017_userprofile_enable_sharing.py b/bookmarks/migrations/0017_userprofile_enable_sharing.py index bc676a2..7a094eb 100644 --- a/bookmarks/migrations/0017_userprofile_enable_sharing.py +++ b/bookmarks/migrations/0017_userprofile_enable_sharing.py @@ -6,13 +6,13 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('bookmarks', '0016_bookmark_shared'), + ("bookmarks", "0016_bookmark_shared"), ] operations = [ migrations.AddField( - model_name='userprofile', - name='enable_sharing', + model_name="userprofile", + name="enable_sharing", field=models.BooleanField(default=False), ), ] diff --git a/bookmarks/migrations/0018_bookmark_favicon_file.py b/bookmarks/migrations/0018_bookmark_favicon_file.py index a61eb22..ce99fcb 100644 --- a/bookmarks/migrations/0018_bookmark_favicon_file.py +++ b/bookmarks/migrations/0018_bookmark_favicon_file.py @@ -6,13 +6,13 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('bookmarks', '0017_userprofile_enable_sharing'), + ("bookmarks", "0017_userprofile_enable_sharing"), ] operations = [ migrations.AddField( - model_name='bookmark', - name='favicon_file', + model_name="bookmark", + name="favicon_file", field=models.CharField(blank=True, max_length=512), ), ] diff --git a/bookmarks/migrations/0019_userprofile_enable_favicons.py b/bookmarks/migrations/0019_userprofile_enable_favicons.py index c64ff87..b0fc382 100644 --- a/bookmarks/migrations/0019_userprofile_enable_favicons.py +++ b/bookmarks/migrations/0019_userprofile_enable_favicons.py @@ -6,13 +6,13 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('bookmarks', '0018_bookmark_favicon_file'), + ("bookmarks", "0018_bookmark_favicon_file"), ] operations = [ migrations.AddField( - model_name='userprofile', - name='enable_favicons', + model_name="userprofile", + name="enable_favicons", field=models.BooleanField(default=False), ), ] diff --git a/bookmarks/migrations/0020_userprofile_tag_search.py b/bookmarks/migrations/0020_userprofile_tag_search.py index 78711e7..13d9adf 100644 --- a/bookmarks/migrations/0020_userprofile_tag_search.py +++ b/bookmarks/migrations/0020_userprofile_tag_search.py @@ -6,13 +6,17 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('bookmarks', '0019_userprofile_enable_favicons'), + ("bookmarks", "0019_userprofile_enable_favicons"), ] operations = [ migrations.AddField( - model_name='userprofile', - name='tag_search', - field=models.CharField(choices=[('strict', 'Strict'), ('lax', 'Lax')], default='strict', max_length=10), + model_name="userprofile", + name="tag_search", + field=models.CharField( + choices=[("strict", "Strict"), ("lax", "Lax")], + default="strict", + max_length=10, + ), ), ] diff --git a/bookmarks/migrations/0021_userprofile_display_url.py b/bookmarks/migrations/0021_userprofile_display_url.py index f44dce3..a4bcf7c 100644 --- a/bookmarks/migrations/0021_userprofile_display_url.py +++ b/bookmarks/migrations/0021_userprofile_display_url.py @@ -6,13 +6,13 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('bookmarks', '0020_userprofile_tag_search'), + ("bookmarks", "0020_userprofile_tag_search"), ] operations = [ migrations.AddField( - model_name='userprofile', - name='display_url', + model_name="userprofile", + name="display_url", field=models.BooleanField(default=False), ), ] diff --git a/bookmarks/migrations/0022_bookmark_notes.py b/bookmarks/migrations/0022_bookmark_notes.py index 4670a61..b98df83 100644 --- a/bookmarks/migrations/0022_bookmark_notes.py +++ b/bookmarks/migrations/0022_bookmark_notes.py @@ -6,13 +6,13 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('bookmarks', '0021_userprofile_display_url'), + ("bookmarks", "0021_userprofile_display_url"), ] operations = [ migrations.AddField( - model_name='bookmark', - name='notes', + model_name="bookmark", + name="notes", field=models.TextField(blank=True), ), ] diff --git a/bookmarks/migrations/0023_userprofile_permanent_notes.py b/bookmarks/migrations/0023_userprofile_permanent_notes.py index 4bfc24e..6b7a1d5 100644 --- a/bookmarks/migrations/0023_userprofile_permanent_notes.py +++ b/bookmarks/migrations/0023_userprofile_permanent_notes.py @@ -6,13 +6,13 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('bookmarks', '0022_bookmark_notes'), + ("bookmarks", "0022_bookmark_notes"), ] operations = [ migrations.AddField( - model_name='userprofile', - name='permanent_notes', + model_name="userprofile", + name="permanent_notes", field=models.BooleanField(default=False), ), ] diff --git a/bookmarks/migrations/0024_userprofile_enable_public_sharing.py b/bookmarks/migrations/0024_userprofile_enable_public_sharing.py index db5f40b..0a964d7 100644 --- a/bookmarks/migrations/0024_userprofile_enable_public_sharing.py +++ b/bookmarks/migrations/0024_userprofile_enable_public_sharing.py @@ -6,13 +6,13 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('bookmarks', '0023_userprofile_permanent_notes'), + ("bookmarks", "0023_userprofile_permanent_notes"), ] operations = [ migrations.AddField( - model_name='userprofile', - name='enable_public_sharing', + model_name="userprofile", + name="enable_public_sharing", field=models.BooleanField(default=False), ), ] diff --git a/bookmarks/migrations/0025_userprofile_search_preferences.py b/bookmarks/migrations/0025_userprofile_search_preferences.py index 8886492..4cf1223 100644 --- a/bookmarks/migrations/0025_userprofile_search_preferences.py +++ b/bookmarks/migrations/0025_userprofile_search_preferences.py @@ -6,13 +6,13 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('bookmarks', '0024_userprofile_enable_public_sharing'), + ("bookmarks", "0024_userprofile_enable_public_sharing"), ] operations = [ migrations.AddField( - model_name='userprofile', - name='search_preferences', + model_name="userprofile", + name="search_preferences", field=models.JSONField(default=dict), ), ] diff --git a/bookmarks/models.py b/bookmarks/models.py index 2da99d7..87711ce 100644 --- a/bookmarks/models.py +++ b/bookmarks/models.py @@ -26,10 +26,10 @@ class Tag(models.Model): def sanitize_tag_name(tag_name: str): # strip leading/trailing spaces # replace inner spaces with replacement char - return tag_name.strip().replace(' ', '-') + return tag_name.strip().replace(" ", "-") -def parse_tag_string(tag_string: str, delimiter: str = ','): +def parse_tag_string(tag_string: str, delimiter: str = ","): if not tag_string: return [] names = tag_string.strip().split(delimiter) @@ -42,7 +42,7 @@ def parse_tag_string(tag_string: str, delimiter: str = ','): return names -def build_tag_string(tag_names: List[str], delimiter: str = ','): +def build_tag_string(tag_names: List[str], delimiter: str = ","): return delimiter.join(tag_names) @@ -82,7 +82,7 @@ class Bookmark(models.Model): return [tag.name for tag in self.tags.all()] def __str__(self): - return self.resolved_title + ' (' + self.url[:30] + '...)' + return self.resolved_title + " (" + self.url[:30] + "...)" class BookmarkForm(forms.ModelForm): @@ -90,15 +90,13 @@ class BookmarkForm(forms.ModelForm): url = forms.CharField(validators=[BookmarkURLValidator()]) tag_string = forms.CharField(required=False) # Do not require title and description in form as we fill these automatically if they are empty - title = forms.CharField(max_length=512, - required=False) - description = forms.CharField(required=False, - widget=forms.Textarea()) + title = forms.CharField(max_length=512, required=False) + description = forms.CharField(required=False, widget=forms.Textarea()) # Include website title and description as hidden field as they only provide info when editing bookmarks - website_title = forms.CharField(max_length=512, - required=False, widget=forms.HiddenInput()) - website_description = forms.CharField(required=False, - widget=forms.HiddenInput()) + website_title = forms.CharField( + max_length=512, required=False, widget=forms.HiddenInput() + ) + website_description = forms.CharField(required=False, widget=forms.HiddenInput()) unread = forms.BooleanField(required=False) shared = forms.BooleanField(required=False) # Hidden field that determines whether to close window/tab after saving the bookmark @@ -107,16 +105,16 @@ class BookmarkForm(forms.ModelForm): class Meta: model = Bookmark fields = [ - 'url', - 'tag_string', - 'title', - 'description', - 'notes', - 'website_title', - 'website_description', - 'unread', - 'shared', - 'auto_close', + "url", + "tag_string", + "title", + "description", + "notes", + "website_title", + "website_description", + "unread", + "shared", + "auto_close", ] @property @@ -125,45 +123,47 @@ class BookmarkForm(forms.ModelForm): class BookmarkSearch: - SORT_ADDED_ASC = 'added_asc' - SORT_ADDED_DESC = 'added_desc' - SORT_TITLE_ASC = 'title_asc' - SORT_TITLE_DESC = 'title_desc' + SORT_ADDED_ASC = "added_asc" + SORT_ADDED_DESC = "added_desc" + SORT_TITLE_ASC = "title_asc" + SORT_TITLE_DESC = "title_desc" - FILTER_SHARED_OFF = 'off' - FILTER_SHARED_SHARED = 'yes' - FILTER_SHARED_UNSHARED = 'no' + FILTER_SHARED_OFF = "off" + FILTER_SHARED_SHARED = "yes" + FILTER_SHARED_UNSHARED = "no" - FILTER_UNREAD_OFF = 'off' - FILTER_UNREAD_YES = 'yes' - FILTER_UNREAD_NO = 'no' + FILTER_UNREAD_OFF = "off" + FILTER_UNREAD_YES = "yes" + FILTER_UNREAD_NO = "no" - params = ['q', 'user', 'sort', 'shared', 'unread'] - preferences = ['sort', 'shared', 'unread'] + params = ["q", "user", "sort", "shared", "unread"] + preferences = ["sort", "shared", "unread"] defaults = { - 'q': '', - 'user': '', - 'sort': SORT_ADDED_DESC, - 'shared': FILTER_SHARED_OFF, - 'unread': FILTER_UNREAD_OFF, + "q": "", + "user": "", + "sort": SORT_ADDED_DESC, + "shared": FILTER_SHARED_OFF, + "unread": FILTER_UNREAD_OFF, } - def __init__(self, - q: str = None, - user: str = None, - sort: str = None, - shared: str = None, - unread: str = None, - preferences: dict = None): + def __init__( + self, + q: str = None, + user: str = None, + sort: str = None, + shared: str = None, + unread: str = None, + preferences: dict = None, + ): if not preferences: preferences = {} self.defaults = {**BookmarkSearch.defaults, **preferences} - self.q = q or self.defaults['q'] - self.user = user or self.defaults['user'] - self.sort = sort or self.defaults['sort'] - self.shared = shared or self.defaults['shared'] - self.unread = unread or self.defaults['unread'] + self.q = q or self.defaults["q"] + self.user = user or self.defaults["user"] + self.sort = sort or self.defaults["sort"] + self.shared = shared or self.defaults["shared"] + self.unread = unread or self.defaults["unread"] def is_modified(self, param): value = self.__dict__[param] @@ -175,7 +175,11 @@ class BookmarkSearch: @property def modified_preferences(self): - return [preference for preference in self.preferences if self.is_modified(preference)] + return [ + preference + for preference in self.preferences + if self.is_modified(preference) + ] @property def has_modifications(self): @@ -191,7 +195,9 @@ class BookmarkSearch: @property def preferences_dict(self): - return {preference: self.__dict__[preference] for preference in self.preferences} + return { + preference: self.__dict__[preference] for preference in self.preferences + } @staticmethod def from_request(query_dict: QueryDict, preferences: dict = None): @@ -206,20 +212,20 @@ class BookmarkSearch: class BookmarkSearchForm(forms.Form): SORT_CHOICES = [ - (BookmarkSearch.SORT_ADDED_ASC, 'Added ↑'), - (BookmarkSearch.SORT_ADDED_DESC, 'Added ↓'), - (BookmarkSearch.SORT_TITLE_ASC, 'Title ↑'), - (BookmarkSearch.SORT_TITLE_DESC, 'Title ↓'), + (BookmarkSearch.SORT_ADDED_ASC, "Added ↑"), + (BookmarkSearch.SORT_ADDED_DESC, "Added ↓"), + (BookmarkSearch.SORT_TITLE_ASC, "Title ↑"), + (BookmarkSearch.SORT_TITLE_DESC, "Title ↓"), ] FILTER_SHARED_CHOICES = [ - (BookmarkSearch.FILTER_SHARED_OFF, 'Off'), - (BookmarkSearch.FILTER_SHARED_SHARED, 'Shared'), - (BookmarkSearch.FILTER_SHARED_UNSHARED, 'Unshared'), + (BookmarkSearch.FILTER_SHARED_OFF, "Off"), + (BookmarkSearch.FILTER_SHARED_SHARED, "Shared"), + (BookmarkSearch.FILTER_SHARED_UNSHARED, "Unshared"), ] FILTER_UNREAD_CHOICES = [ - (BookmarkSearch.FILTER_UNREAD_OFF, 'Off'), - (BookmarkSearch.FILTER_UNREAD_YES, 'Unread'), - (BookmarkSearch.FILTER_UNREAD_NO, 'Read'), + (BookmarkSearch.FILTER_UNREAD_OFF, "Off"), + (BookmarkSearch.FILTER_UNREAD_YES, "Unread"), + (BookmarkSearch.FILTER_UNREAD_NO, "Read"), ] q = forms.CharField() @@ -228,7 +234,12 @@ class BookmarkSearchForm(forms.Form): shared = forms.ChoiceField(choices=FILTER_SHARED_CHOICES, widget=forms.RadioSelect) unread = forms.ChoiceField(choices=FILTER_UNREAD_CHOICES, widget=forms.RadioSelect) - def __init__(self, search: BookmarkSearch, editable_fields: List[str] = None, users: List[User] = None): + def __init__( + self, + search: BookmarkSearch, + editable_fields: List[str] = None, + users: List[User] = None, + ): super().__init__() editable_fields = editable_fields or [] self.editable_fields = editable_fields @@ -236,8 +247,8 @@ class BookmarkSearchForm(forms.Form): # set choices for user field if users are provided if users: user_choices = [(user.username, user.username) for user in users] - user_choices.insert(0, ('', 'Everyone')) - self.fields['user'].choices = user_choices + user_choices.insert(0, ("", "Everyone")) + self.fields["user"].choices = user_choices for param in search.params: # set initial values for modified params @@ -251,50 +262,70 @@ class BookmarkSearchForm(forms.Form): class UserProfile(models.Model): - THEME_AUTO = 'auto' - THEME_LIGHT = 'light' - THEME_DARK = 'dark' + THEME_AUTO = "auto" + THEME_LIGHT = "light" + THEME_DARK = "dark" THEME_CHOICES = [ - (THEME_AUTO, 'Auto'), - (THEME_LIGHT, 'Light'), - (THEME_DARK, 'Dark'), + (THEME_AUTO, "Auto"), + (THEME_LIGHT, "Light"), + (THEME_DARK, "Dark"), ] - BOOKMARK_DATE_DISPLAY_RELATIVE = 'relative' - BOOKMARK_DATE_DISPLAY_ABSOLUTE = 'absolute' - BOOKMARK_DATE_DISPLAY_HIDDEN = 'hidden' + BOOKMARK_DATE_DISPLAY_RELATIVE = "relative" + BOOKMARK_DATE_DISPLAY_ABSOLUTE = "absolute" + BOOKMARK_DATE_DISPLAY_HIDDEN = "hidden" BOOKMARK_DATE_DISPLAY_CHOICES = [ - (BOOKMARK_DATE_DISPLAY_RELATIVE, 'Relative'), - (BOOKMARK_DATE_DISPLAY_ABSOLUTE, 'Absolute'), - (BOOKMARK_DATE_DISPLAY_HIDDEN, 'Hidden'), + (BOOKMARK_DATE_DISPLAY_RELATIVE, "Relative"), + (BOOKMARK_DATE_DISPLAY_ABSOLUTE, "Absolute"), + (BOOKMARK_DATE_DISPLAY_HIDDEN, "Hidden"), ] - BOOKMARK_LINK_TARGET_BLANK = '_blank' - BOOKMARK_LINK_TARGET_SELF = '_self' + BOOKMARK_LINK_TARGET_BLANK = "_blank" + BOOKMARK_LINK_TARGET_SELF = "_self" BOOKMARK_LINK_TARGET_CHOICES = [ - (BOOKMARK_LINK_TARGET_BLANK, 'New page'), - (BOOKMARK_LINK_TARGET_SELF, 'Same page'), + (BOOKMARK_LINK_TARGET_BLANK, "New page"), + (BOOKMARK_LINK_TARGET_SELF, "Same page"), ] - WEB_ARCHIVE_INTEGRATION_DISABLED = 'disabled' - WEB_ARCHIVE_INTEGRATION_ENABLED = 'enabled' + WEB_ARCHIVE_INTEGRATION_DISABLED = "disabled" + WEB_ARCHIVE_INTEGRATION_ENABLED = "enabled" WEB_ARCHIVE_INTEGRATION_CHOICES = [ - (WEB_ARCHIVE_INTEGRATION_DISABLED, 'Disabled'), - (WEB_ARCHIVE_INTEGRATION_ENABLED, 'Enabled'), + (WEB_ARCHIVE_INTEGRATION_DISABLED, "Disabled"), + (WEB_ARCHIVE_INTEGRATION_ENABLED, "Enabled"), ] - TAG_SEARCH_STRICT = 'strict' - TAG_SEARCH_LAX = 'lax' + TAG_SEARCH_STRICT = "strict" + TAG_SEARCH_LAX = "lax" TAG_SEARCH_CHOICES = [ - (TAG_SEARCH_STRICT, 'Strict'), - (TAG_SEARCH_LAX, 'Lax'), + (TAG_SEARCH_STRICT, "Strict"), + (TAG_SEARCH_LAX, "Lax"), ] - user = models.OneToOneField(get_user_model(), related_name='profile', on_delete=models.CASCADE) - theme = models.CharField(max_length=10, choices=THEME_CHOICES, blank=False, default=THEME_AUTO) - bookmark_date_display = models.CharField(max_length=10, choices=BOOKMARK_DATE_DISPLAY_CHOICES, blank=False, - default=BOOKMARK_DATE_DISPLAY_RELATIVE) - bookmark_link_target = models.CharField(max_length=10, choices=BOOKMARK_LINK_TARGET_CHOICES, blank=False, - default=BOOKMARK_LINK_TARGET_BLANK) - web_archive_integration = models.CharField(max_length=10, choices=WEB_ARCHIVE_INTEGRATION_CHOICES, blank=False, - default=WEB_ARCHIVE_INTEGRATION_DISABLED) - tag_search = models.CharField(max_length=10, choices=TAG_SEARCH_CHOICES, blank=False, - default=TAG_SEARCH_STRICT) + user = models.OneToOneField( + get_user_model(), related_name="profile", on_delete=models.CASCADE + ) + theme = models.CharField( + max_length=10, choices=THEME_CHOICES, blank=False, default=THEME_AUTO + ) + bookmark_date_display = models.CharField( + max_length=10, + choices=BOOKMARK_DATE_DISPLAY_CHOICES, + blank=False, + default=BOOKMARK_DATE_DISPLAY_RELATIVE, + ) + bookmark_link_target = models.CharField( + max_length=10, + choices=BOOKMARK_LINK_TARGET_CHOICES, + blank=False, + default=BOOKMARK_LINK_TARGET_BLANK, + ) + web_archive_integration = models.CharField( + max_length=10, + choices=WEB_ARCHIVE_INTEGRATION_CHOICES, + blank=False, + default=WEB_ARCHIVE_INTEGRATION_DISABLED, + ) + tag_search = models.CharField( + max_length=10, + choices=TAG_SEARCH_CHOICES, + blank=False, + default=TAG_SEARCH_STRICT, + ) enable_sharing = models.BooleanField(default=False, null=False) enable_public_sharing = models.BooleanField(default=False, null=False) enable_favicons = models.BooleanField(default=False, null=False) @@ -306,8 +337,18 @@ class UserProfile(models.Model): class UserProfileForm(forms.ModelForm): class Meta: model = UserProfile - fields = ['theme', 'bookmark_date_display', 'bookmark_link_target', 'web_archive_integration', 'tag_search', - 'enable_sharing', 'enable_public_sharing', 'enable_favicons', 'display_url', 'permanent_notes'] + fields = [ + "theme", + "bookmark_date_display", + "bookmark_link_target", + "web_archive_integration", + "tag_search", + "enable_sharing", + "enable_public_sharing", + "enable_favicons", + "display_url", + "permanent_notes", + ] @receiver(post_save, sender=get_user_model()) @@ -332,11 +373,13 @@ class FeedToken(models.Model): """ Adapted from authtoken.models.Token """ + key = models.CharField(max_length=40, primary_key=True) - user = models.OneToOneField(get_user_model(), - related_name='feed_token', - on_delete=models.CASCADE, - ) + user = models.OneToOneField( + get_user_model(), + related_name="feed_token", + on_delete=models.CASCADE, + ) created = models.DateTimeField(auto_now_add=True) def save(self, *args, **kwargs): diff --git a/bookmarks/queries.py b/bookmarks/queries.py index dedeab6..6235e2f 100644 --- a/bookmarks/queries.py +++ b/bookmarks/queries.py @@ -10,18 +10,24 @@ from bookmarks.models import Bookmark, BookmarkSearch, Tag, UserProfile from bookmarks.utils import unique -def query_bookmarks(user: User, profile: UserProfile, search: BookmarkSearch) -> QuerySet: - return _base_bookmarks_query(user, profile, search) \ - .filter(is_archived=False) +def query_bookmarks( + user: User, profile: UserProfile, search: BookmarkSearch +) -> QuerySet: + return _base_bookmarks_query(user, profile, search).filter(is_archived=False) -def query_archived_bookmarks(user: User, profile: UserProfile, search: BookmarkSearch) -> QuerySet: - return _base_bookmarks_query(user, profile, search) \ - .filter(is_archived=True) +def query_archived_bookmarks( + user: User, profile: UserProfile, search: BookmarkSearch +) -> QuerySet: + return _base_bookmarks_query(user, profile, search).filter(is_archived=True) -def query_shared_bookmarks(user: Optional[User], profile: UserProfile, search: BookmarkSearch, - public_only: bool) -> QuerySet: +def query_shared_bookmarks( + user: Optional[User], + profile: UserProfile, + search: BookmarkSearch, + public_only: bool, +) -> QuerySet: conditions = Q(shared=True) & Q(owner__profile__enable_sharing=True) if public_only: conditions = conditions & Q(owner__profile__enable_public_sharing=True) @@ -29,7 +35,9 @@ def query_shared_bookmarks(user: Optional[User], profile: UserProfile, search: B return _base_bookmarks_query(user, profile, search).filter(conditions) -def _base_bookmarks_query(user: Optional[User], profile: UserProfile, search: BookmarkSearch) -> QuerySet: +def _base_bookmarks_query( + user: Optional[User], profile: UserProfile, search: BookmarkSearch +) -> QuerySet: query_set = Bookmark.objects # Filter for user @@ -40,34 +48,32 @@ def _base_bookmarks_query(user: Optional[User], profile: UserProfile, search: Bo query = parse_query_string(search.q) # Filter for search terms and tags - for term in query['search_terms']: - conditions = Q(title__icontains=term) \ - | Q(description__icontains=term) \ - | Q(notes__icontains=term) \ - | Q(website_title__icontains=term) \ - | Q(website_description__icontains=term) \ - | Q(url__icontains=term) + for term in query["search_terms"]: + conditions = ( + Q(title__icontains=term) + | Q(description__icontains=term) + | Q(notes__icontains=term) + | Q(website_title__icontains=term) + | Q(website_description__icontains=term) + | Q(url__icontains=term) + ) if profile.tag_search == UserProfile.TAG_SEARCH_LAX: - conditions = conditions | Exists(Bookmark.objects.filter(id=OuterRef('id'), tags__name__iexact=term)) + conditions = conditions | Exists( + Bookmark.objects.filter(id=OuterRef("id"), tags__name__iexact=term) + ) query_set = query_set.filter(conditions) - for tag_name in query['tag_names']: - query_set = query_set.filter( - tags__name__iexact=tag_name - ) + for tag_name in query["tag_names"]: + query_set = query_set.filter(tags__name__iexact=tag_name) # Untagged bookmarks - if query['untagged']: - query_set = query_set.filter( - tags=None - ) + if query["untagged"]: + query_set = query_set.filter(tags=None) # Legacy unread bookmarks filter from query - if query['unread']: - query_set = query_set.filter( - unread=True - ) + if query["unread"]: + query_set = query_set.filter(unread=True) # Unread filter from bookmark search if search.unread == BookmarkSearch.FILTER_UNREAD_YES: @@ -83,29 +89,36 @@ def _base_bookmarks_query(user: Optional[User], profile: UserProfile, search: Bo # Sort by date added if search.sort == BookmarkSearch.SORT_ADDED_ASC: - query_set = query_set.order_by('date_added') + query_set = query_set.order_by("date_added") elif search.sort == BookmarkSearch.SORT_ADDED_DESC: - query_set = query_set.order_by('-date_added') + query_set = query_set.order_by("-date_added") # Sort by title - if search.sort == BookmarkSearch.SORT_TITLE_ASC or search.sort == BookmarkSearch.SORT_TITLE_DESC: + if ( + search.sort == BookmarkSearch.SORT_TITLE_ASC + or search.sort == BookmarkSearch.SORT_TITLE_DESC + ): # For the title, the resolved_title logic from the Bookmark entity needs # to be replicated as there is no corresponding database field query_set = query_set.annotate( effective_title=Case( - When(Q(title__isnull=False) & ~Q(title__exact=''), then=Lower('title')), - When(Q(website_title__isnull=False) & ~Q(website_title__exact=''), then=Lower('website_title')), - default=Lower('url'), - output_field=CharField() - )) + When(Q(title__isnull=False) & ~Q(title__exact=""), then=Lower("title")), + When( + Q(website_title__isnull=False) & ~Q(website_title__exact=""), + then=Lower("website_title"), + ), + default=Lower("url"), + output_field=CharField(), + ) + ) # For SQLite, if the ICU extension is loaded, use the custom collation # loaded into the connection. This results in an improved sort order for # unicode characters (umlauts, etc.) if settings.USE_SQLITE and settings.USE_SQLITE_ICU_EXTENSION: - order_field = RawSQL('effective_title COLLATE ICU', ()) + order_field = RawSQL("effective_title COLLATE ICU", ()) else: - order_field = 'effective_title' + order_field = "effective_title" if search.sort == BookmarkSearch.SORT_TITLE_ASC: query_set = query_set.order_by(order_field) @@ -115,7 +128,9 @@ def _base_bookmarks_query(user: Optional[User], profile: UserProfile, search: Bo return query_set -def query_bookmark_tags(user: User, profile: UserProfile, search: BookmarkSearch) -> QuerySet: +def query_bookmark_tags( + user: User, profile: UserProfile, search: BookmarkSearch +) -> QuerySet: bookmarks_query = query_bookmarks(user, profile, search) query_set = Tag.objects.filter(bookmark__in=bookmarks_query) @@ -123,7 +138,9 @@ def query_bookmark_tags(user: User, profile: UserProfile, search: BookmarkSearch return query_set.distinct() -def query_archived_bookmark_tags(user: User, profile: UserProfile, search: BookmarkSearch) -> QuerySet: +def query_archived_bookmark_tags( + user: User, profile: UserProfile, search: BookmarkSearch +) -> QuerySet: bookmarks_query = query_archived_bookmarks(user, profile, search) query_set = Tag.objects.filter(bookmark__in=bookmarks_query) @@ -131,8 +148,12 @@ def query_archived_bookmark_tags(user: User, profile: UserProfile, search: Bookm return query_set.distinct() -def query_shared_bookmark_tags(user: Optional[User], profile: UserProfile, search: BookmarkSearch, - public_only: bool) -> QuerySet: +def query_shared_bookmark_tags( + user: Optional[User], + profile: UserProfile, + search: BookmarkSearch, + public_only: bool, +) -> QuerySet: bookmarks_query = query_shared_bookmarks(user, profile, search, public_only) query_set = Tag.objects.filter(bookmark__in=bookmarks_query) @@ -140,7 +161,9 @@ def query_shared_bookmark_tags(user: Optional[User], profile: UserProfile, searc return query_set.distinct() -def query_shared_bookmark_users(profile: UserProfile, search: BookmarkSearch, public_only: bool) -> QuerySet: +def query_shared_bookmark_users( + profile: UserProfile, search: BookmarkSearch, public_only: bool +) -> QuerySet: bookmarks_query = query_shared_bookmarks(None, profile, search, public_only) query_set = User.objects.filter(bookmark__in=bookmarks_query) @@ -155,23 +178,23 @@ def get_user_tags(user: User): def parse_query_string(query_string): # Sanitize query params if not query_string: - query_string = '' + query_string = "" # Split query into search terms and tags - keywords = query_string.strip().split(' ') + keywords = query_string.strip().split(" ") keywords = [word for word in keywords if word] - search_terms = [word for word in keywords if word[0] != '#' and word[0] != '!'] - tag_names = [word[1:] for word in keywords if word[0] == '#'] + search_terms = [word for word in keywords if word[0] != "#" and word[0] != "!"] + tag_names = [word[1:] for word in keywords if word[0] == "#"] tag_names = unique(tag_names, str.lower) # Special search commands - untagged = '!untagged' in keywords - unread = '!unread' in keywords + untagged = "!untagged" in keywords + unread = "!unread" in keywords return { - 'search_terms': search_terms, - 'tag_names': tag_names, - 'untagged': untagged, - 'unread': unread, + "search_terms": search_terms, + "tag_names": tag_names, + "untagged": untagged, + "unread": unread, } diff --git a/bookmarks/services/bookmarks.py b/bookmarks/services/bookmarks.py index 0ba5444..62db657 100644 --- a/bookmarks/services/bookmarks.py +++ b/bookmarks/services/bookmarks.py @@ -11,7 +11,9 @@ from bookmarks.services import tasks def create_bookmark(bookmark: Bookmark, tag_string: str, current_user: User): # If URL is already bookmarked, then update it - existing_bookmark: Bookmark = Bookmark.objects.filter(owner=current_user, url=bookmark.url).first() + existing_bookmark: Bookmark = Bookmark.objects.filter( + owner=current_user, url=bookmark.url + ).first() if existing_bookmark is not None: _merge_bookmark_data(bookmark, existing_bookmark) @@ -68,8 +70,9 @@ def archive_bookmark(bookmark: Bookmark): def archive_bookmarks(bookmark_ids: [Union[int, str]], current_user: User): sanitized_bookmark_ids = _sanitize_id_list(bookmark_ids) - Bookmark.objects.filter(owner=current_user, id__in=sanitized_bookmark_ids).update(is_archived=True, - date_modified=timezone.now()) + Bookmark.objects.filter(owner=current_user, id__in=sanitized_bookmark_ids).update( + is_archived=True, date_modified=timezone.now() + ) def unarchive_bookmark(bookmark: Bookmark): @@ -82,8 +85,9 @@ def unarchive_bookmark(bookmark: Bookmark): def unarchive_bookmarks(bookmark_ids: [Union[int, str]], current_user: User): sanitized_bookmark_ids = _sanitize_id_list(bookmark_ids) - Bookmark.objects.filter(owner=current_user, id__in=sanitized_bookmark_ids).update(is_archived=False, - date_modified=timezone.now()) + Bookmark.objects.filter(owner=current_user, id__in=sanitized_bookmark_ids).update( + is_archived=False, date_modified=timezone.now() + ) def delete_bookmarks(bookmark_ids: [Union[int, str]], current_user: User): @@ -94,8 +98,9 @@ def delete_bookmarks(bookmark_ids: [Union[int, str]], current_user: User): def tag_bookmarks(bookmark_ids: [Union[int, str]], tag_string: str, current_user: User): sanitized_bookmark_ids = _sanitize_id_list(bookmark_ids) - owned_bookmark_ids = Bookmark.objects.filter(owner=current_user, id__in=sanitized_bookmark_ids).values_list('id', - flat=True) + owned_bookmark_ids = Bookmark.objects.filter( + owner=current_user, id__in=sanitized_bookmark_ids + ).values_list("id", flat=True) tag_names = parse_tag_string(tag_string) tags = get_or_create_tags(tag_names, current_user) @@ -103,54 +108,69 @@ def tag_bookmarks(bookmark_ids: [Union[int, str]], tag_string: str, current_user relationships = [] for tag in tags: for bookmark_id in owned_bookmark_ids: - relationships.append(BookmarkToTagRelationShip(bookmark_id=bookmark_id, tag=tag)) + relationships.append( + BookmarkToTagRelationShip(bookmark_id=bookmark_id, tag=tag) + ) # Insert all bookmark -> tag associations at once, should ignore errors if association already exists BookmarkToTagRelationShip.objects.bulk_create(relationships, ignore_conflicts=True) - Bookmark.objects.filter(id__in=owned_bookmark_ids).update(date_modified=timezone.now()) + Bookmark.objects.filter(id__in=owned_bookmark_ids).update( + date_modified=timezone.now() + ) -def untag_bookmarks(bookmark_ids: [Union[int, str]], tag_string: str, current_user: User): +def untag_bookmarks( + bookmark_ids: [Union[int, str]], tag_string: str, current_user: User +): sanitized_bookmark_ids = _sanitize_id_list(bookmark_ids) - owned_bookmark_ids = Bookmark.objects.filter(owner=current_user, id__in=sanitized_bookmark_ids).values_list('id', - flat=True) + owned_bookmark_ids = Bookmark.objects.filter( + owner=current_user, id__in=sanitized_bookmark_ids + ).values_list("id", flat=True) tag_names = parse_tag_string(tag_string) tags = get_or_create_tags(tag_names, current_user) BookmarkToTagRelationShip = Bookmark.tags.through for tag in tags: # Remove all bookmark -> tag associations for the owned bookmarks and the current tag - BookmarkToTagRelationShip.objects.filter(bookmark_id__in=owned_bookmark_ids, tag=tag).delete() + BookmarkToTagRelationShip.objects.filter( + bookmark_id__in=owned_bookmark_ids, tag=tag + ).delete() - Bookmark.objects.filter(id__in=owned_bookmark_ids).update(date_modified=timezone.now()) + Bookmark.objects.filter(id__in=owned_bookmark_ids).update( + date_modified=timezone.now() + ) def mark_bookmarks_as_read(bookmark_ids: [Union[int, str]], current_user: User): sanitized_bookmark_ids = _sanitize_id_list(bookmark_ids) - Bookmark.objects.filter(owner=current_user, id__in=sanitized_bookmark_ids).update(unread=False, - date_modified=timezone.now()) + Bookmark.objects.filter(owner=current_user, id__in=sanitized_bookmark_ids).update( + unread=False, date_modified=timezone.now() + ) def mark_bookmarks_as_unread(bookmark_ids: [Union[int, str]], current_user: User): sanitized_bookmark_ids = _sanitize_id_list(bookmark_ids) - Bookmark.objects.filter(owner=current_user, id__in=sanitized_bookmark_ids).update(unread=True, - date_modified=timezone.now()) + Bookmark.objects.filter(owner=current_user, id__in=sanitized_bookmark_ids).update( + unread=True, date_modified=timezone.now() + ) def share_bookmarks(bookmark_ids: [Union[int, str]], current_user: User): sanitized_bookmark_ids = _sanitize_id_list(bookmark_ids) - Bookmark.objects.filter(owner=current_user, id__in=sanitized_bookmark_ids).update(shared=True, - date_modified=timezone.now()) + Bookmark.objects.filter(owner=current_user, id__in=sanitized_bookmark_ids).update( + shared=True, date_modified=timezone.now() + ) def unshare_bookmarks(bookmark_ids: [Union[int, str]], current_user: User): sanitized_bookmark_ids = _sanitize_id_list(bookmark_ids) - Bookmark.objects.filter(owner=current_user, id__in=sanitized_bookmark_ids).update(shared=False, - date_modified=timezone.now()) + Bookmark.objects.filter(owner=current_user, id__in=sanitized_bookmark_ids).update( + shared=False, date_modified=timezone.now() + ) def _merge_bookmark_data(from_bookmark: Bookmark, to_bookmark: Bookmark): diff --git a/bookmarks/services/exporter.py b/bookmarks/services/exporter.py index dc0717f..ebc3632 100644 --- a/bookmarks/services/exporter.py +++ b/bookmarks/services/exporter.py @@ -13,40 +13,41 @@ def export_netscape_html(bookmarks: List[Bookmark]): [append_bookmark(doc, bookmark) for bookmark in bookmarks] append_list_end(doc) - return '\n\r'.join(doc) + return "\n\r".join(doc) def append_header(doc: BookmarkDocument): - doc.append('') + doc.append("") doc.append('') - doc.append('Bookmarks') - doc.append('

Bookmarks

') + doc.append("Bookmarks") + doc.append("

Bookmarks

") def append_list_start(doc: BookmarkDocument): - doc.append('

') + doc.append("

") def append_bookmark(doc: BookmarkDocument, bookmark: Bookmark): url = bookmark.url - title = html.escape(bookmark.resolved_title or '') - desc = html.escape(bookmark.resolved_description or '') + title = html.escape(bookmark.resolved_title or "") + desc = html.escape(bookmark.resolved_description or "") if bookmark.notes: - desc += f'[linkding-notes]{html.escape(bookmark.notes)}[/linkding-notes]' + desc += f"[linkding-notes]{html.escape(bookmark.notes)}[/linkding-notes]" tag_names = bookmark.tag_names if bookmark.is_archived: - tag_names.append('linkding:archived') - tags = ','.join(tag_names) - toread = '1' if bookmark.unread else '0' - private = '0' if bookmark.shared else '1' + tag_names.append("linkding:archived") + tags = ",".join(tag_names) + toread = "1" if bookmark.unread else "0" + private = "0" if bookmark.shared else "1" added = int(bookmark.date_added.timestamp()) doc.append( - f'

{title}') + f'
{title}' + ) if desc: - doc.append(f'
{desc}') + doc.append(f"
{desc}") def append_list_end(doc: BookmarkDocument): - doc.append('

') + doc.append("

") diff --git a/bookmarks/services/favicon_loader.py b/bookmarks/services/favicon_loader.py index 6534880..2798eb4 100644 --- a/bookmarks/services/favicon_loader.py +++ b/bookmarks/services/favicon_loader.py @@ -15,7 +15,7 @@ logger = logging.getLogger(__name__) # register mime type for .ico files, which is not included in the default # mimetypes of the Docker image -mimetypes.add_type('image/x-icon', '.ico') +mimetypes.add_type("image/x-icon", ".ico") def _ensure_favicon_folder(): @@ -23,16 +23,16 @@ def _ensure_favicon_folder(): def _url_to_filename(url: str) -> str: - return re.sub(r'\W+', '_', url) + return re.sub(r"\W+", "_", url) def _get_url_parameters(url: str) -> dict: parsed_uri = urlparse(url) return { # https://example.com/foo?bar -> https://example.com - 'url': f'{parsed_uri.scheme}://{parsed_uri.hostname}', + "url": f"{parsed_uri.scheme}://{parsed_uri.hostname}", # https://example.com/foo?bar -> example.com - 'domain': parsed_uri.hostname, + "domain": parsed_uri.hostname, } @@ -63,21 +63,21 @@ def load_favicon(url: str) -> str: # Create favicon folder if not exists _ensure_favicon_folder() # Use scheme+hostname as favicon filename to reuse icon for all pages on the same domain - favicon_name = _url_to_filename(url_parameters['url']) + favicon_name = _url_to_filename(url_parameters["url"]) favicon_file = _check_existing_favicon(favicon_name) if not favicon_file: # Load favicon from provider, save to file favicon_url = settings.LD_FAVICON_PROVIDER.format(**url_parameters) - logger.debug(f'Loading favicon from: {favicon_url}') + logger.debug(f"Loading favicon from: {favicon_url}") with requests.get(favicon_url, stream=True) as response: - content_type = response.headers['Content-Type'] + content_type = response.headers["Content-Type"] file_extension = mimetypes.guess_extension(content_type) - favicon_file = f'{favicon_name}{file_extension}' + favicon_file = f"{favicon_name}{file_extension}" favicon_path = _get_favicon_path(favicon_file) - with open(favicon_path, 'wb') as file: + with open(favicon_path, "wb") as file: for chunk in response.iter_content(chunk_size=8192): file.write(chunk) - logger.debug(f'Saved favicon as: {favicon_path}') + logger.debug(f"Saved favicon as: {favicon_path}") return favicon_file diff --git a/bookmarks/services/importer.py b/bookmarks/services/importer.py index 96f8fd0..cdef8f5 100644 --- a/bookmarks/services/importer.py +++ b/bookmarks/services/importer.py @@ -55,18 +55,20 @@ class TagCache: self.cache[tag.name.lower()] = tag -def import_netscape_html(html: str, user: User, options: ImportOptions = ImportOptions()) -> ImportResult: +def import_netscape_html( + html: str, user: User, options: ImportOptions = ImportOptions() +) -> ImportResult: result = ImportResult() import_start = timezone.now() try: netscape_bookmarks = parse(html) except: - logging.exception('Could not read bookmarks file.') + logging.exception("Could not read bookmarks file.") raise parse_end = timezone.now() - logger.debug(f'Parse duration: {parse_end - import_start}') + logger.debug(f"Parse duration: {parse_end - import_start}") # Create and cache all tags beforehand _create_missing_tags(netscape_bookmarks, user) @@ -83,7 +85,7 @@ def import_netscape_html(html: str, user: User, options: ImportOptions = ImportO tasks.schedule_bookmarks_without_favicons(user) end = timezone.now() - logger.debug(f'Import duration: {end - import_start}') + logger.debug(f"Import duration: {end - import_start}") return result @@ -110,7 +112,7 @@ def _get_batches(items: List, batch_size: int): num_items = len(items) while offset < num_items: - batch = items[offset:min(offset + batch_size, num_items)] + batch = items[offset : min(offset + batch_size, num_items)] if len(batch) > 0: batches.append(batch) offset = offset + batch_size @@ -118,11 +120,13 @@ def _get_batches(items: List, batch_size: int): return batches -def _import_batch(netscape_bookmarks: List[NetscapeBookmark], - user: User, - options: ImportOptions, - tag_cache: TagCache, - result: ImportResult): +def _import_batch( + netscape_bookmarks: List[NetscapeBookmark], + user: User, + options: ImportOptions, + tag_cache: TagCache, + result: ImportResult, +): # Query existing bookmarks batch_urls = [bookmark.href for bookmark in netscape_bookmarks] existing_bookmarks = Bookmark.objects.filter(owner=user, url__in=batch_urls) @@ -136,7 +140,13 @@ def _import_batch(netscape_bookmarks: List[NetscapeBookmark], try: # Lookup existing bookmark by URL, or create new bookmark if there is no bookmark for that URL yet bookmark = next( - (bookmark for bookmark in existing_bookmarks if bookmark.url == netscape_bookmark.href), None) + ( + bookmark + for bookmark in existing_bookmarks + if bookmark.url == netscape_bookmark.href + ), + None, + ) if not bookmark: bookmark = Bookmark(owner=user) is_update = False @@ -146,7 +156,7 @@ def _import_batch(netscape_bookmarks: List[NetscapeBookmark], _copy_bookmark_data(netscape_bookmark, bookmark, options) # Validate bookmark fields, exclude owner to prevent n+1 database query, # also there is no specific validation on owner - bookmark.clean_fields(exclude=['owner']) + bookmark.clean_fields(exclude=["owner"]) # Schedule for update or insert if is_update: bookmarks_to_update.append(bookmark) @@ -155,20 +165,25 @@ def _import_batch(netscape_bookmarks: List[NetscapeBookmark], result.success = result.success + 1 except: - shortened_bookmark_tag_str = str(netscape_bookmark)[:100] + '...' - logging.exception('Error importing bookmark: ' + shortened_bookmark_tag_str) + shortened_bookmark_tag_str = str(netscape_bookmark)[:100] + "..." + logging.exception("Error importing bookmark: " + shortened_bookmark_tag_str) result.failed = result.failed + 1 # Bulk update bookmarks in DB - Bookmark.objects.bulk_update(bookmarks_to_update, ['url', - 'date_added', - 'date_modified', - 'unread', - 'shared', - 'title', - 'description', - 'notes', - 'owner']) + Bookmark.objects.bulk_update( + bookmarks_to_update, + [ + "url", + "date_added", + "date_modified", + "unread", + "shared", + "title", + "description", + "notes", + "owner", + ], + ) # Bulk insert new bookmarks into DB Bookmark.objects.bulk_create(bookmarks_to_create) @@ -183,13 +198,20 @@ def _import_batch(netscape_bookmarks: List[NetscapeBookmark], for netscape_bookmark in netscape_bookmarks: # Lookup bookmark by URL again bookmark = next( - (bookmark for bookmark in existing_bookmarks if bookmark.url == netscape_bookmark.href), None) + ( + bookmark + for bookmark in existing_bookmarks + if bookmark.url == netscape_bookmark.href + ), + None, + ) if not bookmark: # Something is wrong, we should have just created this bookmark - shortened_bookmark_tag_str = str(netscape_bookmark)[:100] + '...' + shortened_bookmark_tag_str = str(netscape_bookmark)[:100] + "..." logging.warning( - f'Failed to assign tags to the bookmark: {shortened_bookmark_tag_str}. Could not find bookmark by URL.') + f"Failed to assign tags to the bookmark: {shortened_bookmark_tag_str}. Could not find bookmark by URL." + ) continue # Get tag models by string, schedule inserts for bookmark -> tag associations @@ -201,7 +223,9 @@ def _import_batch(netscape_bookmarks: List[NetscapeBookmark], BookmarkToTagRelationShip.objects.bulk_create(relationships, ignore_conflicts=True) -def _copy_bookmark_data(netscape_bookmark: NetscapeBookmark, bookmark: Bookmark, options: ImportOptions): +def _copy_bookmark_data( + netscape_bookmark: NetscapeBookmark, bookmark: Bookmark, options: ImportOptions +): bookmark.url = netscape_bookmark.href if netscape_bookmark.date_added: bookmark.date_added = parse_timestamp(netscape_bookmark.date_added) diff --git a/bookmarks/services/parser.py b/bookmarks/services/parser.py index 0004dbc..81c4a41 100644 --- a/bookmarks/services/parser.py +++ b/bookmarks/services/parser.py @@ -25,29 +25,29 @@ class BookmarkParser(HTMLParser): self.current_tag = None self.bookmark = None - self.href = '' - self.add_date = '' - self.tags = '' - self.title = '' - self.description = '' - self.notes = '' - self.toread = '' - self.private = '' + self.href = "" + self.add_date = "" + self.tags = "" + self.title = "" + self.description = "" + self.notes = "" + self.toread = "" + self.private = "" def handle_starttag(self, tag: str, attrs: list): - name = 'handle_start_' + tag.lower() + name = "handle_start_" + tag.lower() if name in dir(self): getattr(self, name)({k.lower(): v for k, v in attrs}) self.current_tag = tag def handle_endtag(self, tag: str): - name = 'handle_end_' + tag.lower() + name = "handle_end_" + tag.lower() if name in dir(self): getattr(self, name)() self.current_tag = None def handle_data(self, data): - name = f'handle_{self.current_tag}_data' + name = f"handle_{self.current_tag}_data" if name in dir(self): getattr(self, name)(data) @@ -60,22 +60,22 @@ class BookmarkParser(HTMLParser): def handle_start_a(self, attrs: Dict[str, str]): vars(self).update(attrs) tag_names = parse_tag_string(self.tags) - archived = 'linkding:archived' in self.tags + archived = "linkding:archived" in self.tags try: - tag_names.remove('linkding:archived') + tag_names.remove("linkding:archived") except ValueError: pass self.bookmark = NetscapeBookmark( href=self.href, - title='', - description='', - notes='', + title="", + description="", + notes="", date_added=self.add_date, tag_names=tag_names, - to_read=self.toread == '1', + to_read=self.toread == "1", # Mark as private by default, also when attribute is not specified - private=self.private != '0', + private=self.private != "0", archived=archived, ) @@ -84,9 +84,9 @@ class BookmarkParser(HTMLParser): def handle_dd_data(self, data): desc = data.strip() - if '[linkding-notes]' in desc: - self.notes = desc.split('[linkding-notes]')[1].split('[/linkding-notes]')[0] - self.description = desc.split('[linkding-notes]')[0] + if "[linkding-notes]" in desc: + self.notes = desc.split("[linkding-notes]")[1].split("[/linkding-notes]")[0] + self.description = desc.split("[linkding-notes]")[0] def add_bookmark(self): if self.bookmark: @@ -95,14 +95,14 @@ class BookmarkParser(HTMLParser): self.bookmark.notes = self.notes self.bookmarks.append(self.bookmark) self.bookmark = None - self.href = '' - self.add_date = '' - self.tags = '' - self.title = '' - self.description = '' - self.notes = '' - self.toread = '' - self.private = '' + self.href = "" + self.add_date = "" + self.tags = "" + self.title = "" + self.description = "" + self.notes = "" + self.toread = "" + self.private = "" def parse(html: str) -> List[NetscapeBookmark]: diff --git a/bookmarks/services/tags.py b/bookmarks/services/tags.py index d79a7fe..9d1cee1 100644 --- a/bookmarks/services/tags.py +++ b/bookmarks/services/tags.py @@ -13,7 +13,7 @@ logger = logging.getLogger(__name__) def get_or_create_tags(tag_names: List[str], user: User): tags = [get_or_create_tag(tag_name, user) for tag_name in tag_names] - return unique(tags, operator.attrgetter('id')) + return unique(tags, operator.attrgetter("id")) def get_or_create_tag(name: str, user: User): diff --git a/bookmarks/services/tasks.py b/bookmarks/services/tasks.py index 3a57bf0..cb02e64 100644 --- a/bookmarks/services/tasks.py +++ b/bookmarks/services/tasks.py @@ -18,8 +18,10 @@ logger = logging.getLogger(__name__) def is_web_archive_integration_active(user: User) -> bool: background_tasks_enabled = not settings.LD_DISABLE_BACKGROUND_TASKS - web_archive_integration_enabled = \ - user.profile.web_archive_integration == UserProfile.WEB_ARCHIVE_INTEGRATION_ENABLED + web_archive_integration_enabled = ( + user.profile.web_archive_integration + == UserProfile.WEB_ARCHIVE_INTEGRATION_ENABLED + ) return background_tasks_enabled and web_archive_integration_enabled @@ -31,28 +33,36 @@ def create_web_archive_snapshot(user: User, bookmark: Bookmark, force_update: bo def _load_newest_snapshot(bookmark: Bookmark): try: - logger.info(f'Load existing snapshot for bookmark. url={bookmark.url}') - cdx_api = bookmarks.services.wayback.CustomWaybackMachineCDXServerAPI(bookmark.url) + logger.info(f"Load existing snapshot for bookmark. url={bookmark.url}") + cdx_api = bookmarks.services.wayback.CustomWaybackMachineCDXServerAPI( + bookmark.url + ) existing_snapshot = cdx_api.newest() if existing_snapshot: bookmark.web_archive_snapshot_url = existing_snapshot.archive_url - bookmark.save(update_fields=['web_archive_snapshot_url']) - logger.info(f'Using newest snapshot. url={bookmark.url} from={existing_snapshot.datetime_timestamp}') + bookmark.save(update_fields=["web_archive_snapshot_url"]) + logger.info( + f"Using newest snapshot. url={bookmark.url} from={existing_snapshot.datetime_timestamp}" + ) except NoCDXRecordFound: - logger.info(f'Could not find any snapshots for bookmark. url={bookmark.url}') + logger.info(f"Could not find any snapshots for bookmark. url={bookmark.url}") except WaybackError as error: - logger.error(f'Failed to load existing snapshot. url={bookmark.url}', exc_info=error) + logger.error( + f"Failed to load existing snapshot. url={bookmark.url}", exc_info=error + ) def _create_snapshot(bookmark: Bookmark): - logger.info(f'Create new snapshot for bookmark. url={bookmark.url}...') - archive = waybackpy.WaybackMachineSaveAPI(bookmark.url, DEFAULT_USER_AGENT, max_tries=1) + logger.info(f"Create new snapshot for bookmark. url={bookmark.url}...") + archive = waybackpy.WaybackMachineSaveAPI( + bookmark.url, DEFAULT_USER_AGENT, max_tries=1 + ) archive.save() bookmark.web_archive_snapshot_url = archive.archive_url - bookmark.save(update_fields=['web_archive_snapshot_url']) - logger.info(f'Successfully created new snapshot for bookmark:. url={bookmark.url}') + bookmark.save(update_fields=["web_archive_snapshot_url"]) + logger.info(f"Successfully created new snapshot for bookmark:. url={bookmark.url}") @background() @@ -72,10 +82,13 @@ def _create_web_archive_snapshot_task(bookmark_id: int, force_update: bool): return except TooManyRequestsError: logger.error( - f'Failed to create snapshot due to rate limiting, trying to load newest snapshot as fallback. url={bookmark.url}') + f"Failed to create snapshot due to rate limiting, trying to load newest snapshot as fallback. url={bookmark.url}" + ) except WaybackError as error: - logger.error(f'Failed to create snapshot, trying to load newest snapshot as fallback. url={bookmark.url}', - exc_info=error) + logger.error( + f"Failed to create snapshot, trying to load newest snapshot as fallback. url={bookmark.url}", + exc_info=error, + ) # Load the newest snapshot as fallback _load_newest_snapshot(bookmark) @@ -102,7 +115,9 @@ def schedule_bookmarks_without_snapshots(user: User): @background() def _schedule_bookmarks_without_snapshots_task(user_id: int): user = get_user_model().objects.get(id=user_id) - bookmarks_without_snapshots = Bookmark.objects.filter(web_archive_snapshot_url__exact='', owner=user) + bookmarks_without_snapshots = Bookmark.objects.filter( + web_archive_snapshot_url__exact="", owner=user + ) for bookmark in bookmarks_without_snapshots: # To prevent rate limit errors from the Wayback API only try to load the latest snapshots instead of creating @@ -128,14 +143,16 @@ def _load_favicon_task(bookmark_id: int): except Bookmark.DoesNotExist: return - logger.info(f'Load favicon for bookmark. url={bookmark.url}') + logger.info(f"Load favicon for bookmark. url={bookmark.url}") new_favicon_file = favicon_loader.load_favicon(bookmark.url) if new_favicon_file != bookmark.favicon_file: bookmark.favicon_file = new_favicon_file - bookmark.save(update_fields=['favicon_file']) - logger.info(f'Successfully updated favicon for bookmark. url={bookmark.url} icon={new_favicon_file}') + bookmark.save(update_fields=["favicon_file"]) + logger.info( + f"Successfully updated favicon for bookmark. url={bookmark.url} icon={new_favicon_file}" + ) def schedule_bookmarks_without_favicons(user: User): @@ -146,11 +163,13 @@ def schedule_bookmarks_without_favicons(user: User): @background() def _schedule_bookmarks_without_favicons_task(user_id: int): user = get_user_model().objects.get(id=user_id) - bookmarks = Bookmark.objects.filter(favicon_file__exact='', owner=user) + bookmarks = Bookmark.objects.filter(favicon_file__exact="", owner=user) tasks = [] for bookmark in bookmarks: - task = Task.objects.new_task(task_name='bookmarks.services.tasks._load_favicon_task', args=(bookmark.id,)) + task = Task.objects.new_task( + task_name="bookmarks.services.tasks._load_favicon_task", args=(bookmark.id,) + ) tasks.append(task) Task.objects.bulk_create(tasks) @@ -168,7 +187,9 @@ def _schedule_refresh_favicons_task(user_id: int): tasks = [] for bookmark in bookmarks: - task = Task.objects.new_task(task_name='bookmarks.services.tasks._load_favicon_task', args=(bookmark.id,)) + task = Task.objects.new_task( + task_name="bookmarks.services.tasks._load_favicon_task", args=(bookmark.id,) + ) tasks.append(task) Task.objects.bulk_create(tasks) diff --git a/bookmarks/services/wayback.py b/bookmarks/services/wayback.py index d830403..b527b07 100644 --- a/bookmarks/services/wayback.py +++ b/bookmarks/services/wayback.py @@ -14,8 +14,10 @@ class CustomWaybackMachineCDXServerAPI(waybackpy.WaybackMachineCDXServerAPI): def newest(self): unix_timestamp = int(time.time()) - self.closest = waybackpy.utils.unix_timestamp_to_wayback_timestamp(unix_timestamp) - self.sort = 'closest' + self.closest = waybackpy.utils.unix_timestamp_to_wayback_timestamp( + unix_timestamp + ) + self.sort = "closest" self.limit = -5 newest_snapshot = None @@ -37,4 +39,4 @@ class CustomWaybackMachineCDXServerAPI(waybackpy.WaybackMachineCDXServerAPI): super().add_payload(payload) # Set fastLatest query param, as we are only using this API to get the latest snapshot and using fastLatest # makes searching for latest snapshots faster - payload['fastLatest'] = 'true' + payload["fastLatest"] = "true" diff --git a/bookmarks/services/website_loader.py b/bookmarks/services/website_loader.py index 7549c2c..644e2ea 100644 --- a/bookmarks/services/website_loader.py +++ b/bookmarks/services/website_loader.py @@ -18,9 +18,9 @@ class WebsiteMetadata: def to_dict(self): return { - 'url': self.url, - 'title': self.title, - 'description': self.description, + "url": self.url, + "title": self.title, + "description": self.description, } @@ -34,22 +34,29 @@ def load_website_metadata(url: str): start = timezone.now() page_text = load_page(url) end = timezone.now() - logger.debug(f'Load duration: {end - start}') + logger.debug(f"Load duration: {end - start}") start = timezone.now() - soup = BeautifulSoup(page_text, 'html.parser') + soup = BeautifulSoup(page_text, "html.parser") title = soup.title.string.strip() if soup.title is not None else None - description_tag = soup.find('meta', attrs={'name': 'description'}) - description = description_tag['content'].strip() if description_tag and description_tag[ - 'content'] else None + description_tag = soup.find("meta", attrs={"name": "description"}) + description = ( + description_tag["content"].strip() + if description_tag and description_tag["content"] + else None + ) if not description: - description_tag = soup.find('meta', attrs={'property': 'og:description'}) - description = description_tag['content'].strip() if description_tag and description_tag['content'] else None + description_tag = soup.find("meta", attrs={"property": "og:description"}) + description = ( + description_tag["content"].strip() + if description_tag and description_tag["content"] + else None + ) end = timezone.now() - logger.debug(f'Parsing duration: {end - start}') + logger.debug(f"Parsing duration: {end - start}") finally: return WebsiteMetadata(url=url, title=title, description=description) @@ -73,30 +80,30 @@ def load_page(url: str): else: content = content + chunk - logger.debug(f'Loaded chunk (iteration={iteration}, total={size / 1024})') + logger.debug(f"Loaded chunk (iteration={iteration}, total={size / 1024})") # Stop reading if we have parsed end of head tag - end_of_head = ''.encode('utf-8') + end_of_head = "".encode("utf-8") if end_of_head in content: - logger.debug(f'Found closing head tag after {size} bytes') + logger.debug(f"Found closing head tag after {size} bytes") content = content.split(end_of_head)[0] + end_of_head break # Stop reading if we exceed limit if size > MAX_CONTENT_LIMIT: - logger.debug(f'Cancel reading document after {size} bytes') + logger.debug(f"Cancel reading document after {size} bytes") break - if hasattr(r, '_content_consumed'): - logger.debug(f'Request consumed: {r._content_consumed}') + if hasattr(r, "_content_consumed"): + logger.debug(f"Request consumed: {r._content_consumed}") # Use charset_normalizer to determine encoding that best matches the response content # Several sites seem to specify the response encoding incorrectly, so we ignore it and use custom logic instead # This is different from Response.text which does respect the encoding specified in the response first, # before trying to determine one - results = from_bytes(content or '') + results = from_bytes(content or "") return str(results.best()) -DEFAULT_USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.0.0 Safari/537.36' +DEFAULT_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.0.0 Safari/537.36" def fake_request_headers(): diff --git a/bookmarks/signals.py b/bookmarks/signals.py index 0fd3b15..8507c84 100644 --- a/bookmarks/signals.py +++ b/bookmarks/signals.py @@ -15,9 +15,11 @@ def user_logged_in(sender, request, user, **kwargs): def extend_sqlite(connection=None, **kwargs): # Load ICU extension into Sqlite connection to support case-insensitive # comparisons with unicode characters - if connection.vendor == 'sqlite' and settings.USE_SQLITE_ICU_EXTENSION: + if connection.vendor == "sqlite" and settings.USE_SQLITE_ICU_EXTENSION: connection.connection.enable_load_extension(True) - connection.connection.load_extension(settings.SQLITE_ICU_EXTENSION_PATH.rstrip('.so')) + connection.connection.load_extension( + settings.SQLITE_ICU_EXTENSION_PATH.rstrip(".so") + ) with connection.cursor() as cursor: # Load an ICU collation for case-insensitive ordering. diff --git a/bookmarks/templatetags/bookmarks.py b/bookmarks/templatetags/bookmarks.py index c8fcb07..af7f929 100644 --- a/bookmarks/templatetags/bookmarks.py +++ b/bookmarks/templatetags/bookmarks.py @@ -2,48 +2,67 @@ from typing import List from django import template -from bookmarks.models import BookmarkForm, BookmarkSearch, BookmarkSearchForm, Tag, build_tag_string, User +from bookmarks.models import ( + BookmarkForm, + BookmarkSearch, + BookmarkSearchForm, + Tag, + build_tag_string, + User, +) register = template.Library() -@register.inclusion_tag('bookmarks/form.html', name='bookmark_form', takes_context=True) -def bookmark_form(context, form: BookmarkForm, cancel_url: str, bookmark_id: int = 0, auto_close: bool = False): +@register.inclusion_tag("bookmarks/form.html", name="bookmark_form", takes_context=True) +def bookmark_form( + context, + form: BookmarkForm, + cancel_url: str, + bookmark_id: int = 0, + auto_close: bool = False, +): return { - 'request': context['request'], - 'form': form, - 'auto_close': auto_close, - 'bookmark_id': bookmark_id, - 'cancel_url': cancel_url + "request": context["request"], + "form": form, + "auto_close": auto_close, + "bookmark_id": bookmark_id, + "cancel_url": cancel_url, } -@register.inclusion_tag('bookmarks/search.html', name='bookmark_search', takes_context=True) -def bookmark_search(context, search: BookmarkSearch, tags: [Tag], mode: str = ''): +@register.inclusion_tag( + "bookmarks/search.html", name="bookmark_search", takes_context=True +) +def bookmark_search(context, search: BookmarkSearch, tags: [Tag], mode: str = ""): tag_names = [tag.name for tag in tags] - tags_string = build_tag_string(tag_names, ' ') - search_form = BookmarkSearchForm(search, editable_fields=['q']) + tags_string = build_tag_string(tag_names, " ") + search_form = BookmarkSearchForm(search, editable_fields=["q"]) - if mode == 'shared': - preferences_form = BookmarkSearchForm(search, editable_fields=['sort']) + if mode == "shared": + preferences_form = BookmarkSearchForm(search, editable_fields=["sort"]) else: - preferences_form = BookmarkSearchForm(search, editable_fields=['sort', 'shared', 'unread']) + preferences_form = BookmarkSearchForm( + search, editable_fields=["sort", "shared", "unread"] + ) return { - 'request': context['request'], - 'search': search, - 'search_form': search_form, - 'preferences_form': preferences_form, - 'tags_string': tags_string, - 'mode': mode, + "request": context["request"], + "search": search, + "search_form": search_form, + "preferences_form": preferences_form, + "tags_string": tags_string, + "mode": mode, } -@register.inclusion_tag('bookmarks/user_select.html', name='user_select', takes_context=True) +@register.inclusion_tag( + "bookmarks/user_select.html", name="user_select", takes_context=True +) def user_select(context, search: BookmarkSearch, users: List[User]): sorted_users = sorted(users, key=lambda x: str.lower(x.username)) - form = BookmarkSearchForm(search, editable_fields=['user'], users=sorted_users) + form = BookmarkSearchForm(search, editable_fields=["user"], users=sorted_users) return { - 'search': search, - 'users': sorted_users, - 'form': form, + "search": search, + "users": sorted_users, + "form": form, } diff --git a/bookmarks/templatetags/pagination.py b/bookmarks/templatetags/pagination.py index 5d6fbd7..eff5900 100644 --- a/bookmarks/templatetags/pagination.py +++ b/bookmarks/templatetags/pagination.py @@ -8,14 +8,15 @@ NUM_ADJACENT_PAGES = 2 register = template.Library() -@register.inclusion_tag('bookmarks/pagination.html', name='pagination', takes_context=True) +@register.inclusion_tag( + "bookmarks/pagination.html", name="pagination", takes_context=True +) def pagination(context, page: Page): - visible_page_numbers = get_visible_page_numbers(page.number, page.paginator.num_pages) + visible_page_numbers = get_visible_page_numbers( + page.number, page.paginator.num_pages + ) - return { - 'page': page, - 'visible_page_numbers': visible_page_numbers - } + return {"page": page, "visible_page_numbers": visible_page_numbers} def get_visible_page_numbers(current_page_number: int, num_pages: int) -> [int]: @@ -29,10 +30,12 @@ def get_visible_page_numbers(current_page_number: int, num_pages: int) -> [int]: visible_pages = set() # Add adjacent pages around current page - visible_pages |= set(range( - max(1, current_page_number - NUM_ADJACENT_PAGES), - min(num_pages, current_page_number + NUM_ADJACENT_PAGES) + 1 - )) + visible_pages |= set( + range( + max(1, current_page_number - NUM_ADJACENT_PAGES), + min(num_pages, current_page_number + NUM_ADJACENT_PAGES) + 1, + ) + ) # Add first page visible_pages.add(1) diff --git a/bookmarks/templatetags/shared.py b/bookmarks/templatetags/shared.py index c6acf22..6524c11 100644 --- a/bookmarks/templatetags/shared.py +++ b/bookmarks/templatetags/shared.py @@ -28,12 +28,12 @@ def add_tag_to_query(context, tag_name: str): params = context.request.GET.copy() # Append to or create query string - if params.__contains__('q'): - query_string = params.__getitem__('q') + ' ' + if params.__contains__("q"): + query_string = params.__getitem__("q") + " " else: - query_string = '' - query_string = query_string + '#' + tag_name - params.__setitem__('q', query_string) + query_string = "" + query_string = query_string + "#" + tag_name + params.__setitem__("q", query_string) return params.urlencode() @@ -41,20 +41,26 @@ def add_tag_to_query(context, tag_name: str): @register.simple_tag(takes_context=True) def remove_tag_from_query(context, tag_name: str): params = context.request.GET.copy() - if params.__contains__('q'): + if params.__contains__("q"): # Split query string into parts - query_string = params.__getitem__('q') + query_string = params.__getitem__("q") query_parts = query_string.split() # Remove tag with hash - tag_name_with_hash = '#' + tag_name - query_parts = [part for part in query_parts if str.lower(part) != str.lower(tag_name_with_hash)] + tag_name_with_hash = "#" + tag_name + query_parts = [ + part + for part in query_parts + if str.lower(part) != str.lower(tag_name_with_hash) + ] # When using lax tag search, also remove tag without hash profile = context.request.user_profile if profile.tag_search == UserProfile.TAG_SEARCH_LAX: - query_parts = [part for part in query_parts if str.lower(part) != str.lower(tag_name)] + query_parts = [ + part for part in query_parts if str.lower(part) != str.lower(tag_name) + ] # Rebuild query string - query_string = ' '.join(query_parts) - params.__setitem__('q', query_string) + query_string = " ".join(query_parts) + params.__setitem__("q", query_string) return params.urlencode() @@ -71,38 +77,38 @@ def replace_query_param(context, **kwargs): return query.urlencode() -@register.filter(name='hash_tag') +@register.filter(name="hash_tag") def hash_tag(tag_name): - return '#' + tag_name + return "#" + tag_name -@register.filter(name='first_char') +@register.filter(name="first_char") def first_char(text): return text[0] -@register.filter(name='remaining_chars') +@register.filter(name="remaining_chars") def remaining_chars(text, index): return text[index:] -@register.filter(name='humanize_absolute_date') +@register.filter(name="humanize_absolute_date") def humanize_absolute_date(value): - if value in (None, ''): - return '' + if value in (None, ""): + return "" return utils.humanize_absolute_date(value) -@register.filter(name='humanize_relative_date') +@register.filter(name="humanize_relative_date") def humanize_relative_date(value): - if value in (None, ''): - return '' + if value in (None, ""): + return "" return utils.humanize_relative_date(value) @register.tag def htmlmin(parser, token): - nodelist = parser.parse(('endhtmlmin',)) + nodelist = parser.parse(("endhtmlmin",)) parser.delete_first_token() return HtmlMinNode(nodelist) @@ -114,7 +120,7 @@ class HtmlMinNode(template.Node): def render(self, context): output = self.nodelist.render(context) - output = re.sub(r'\s+', ' ', output) + output = re.sub(r"\s+", " ", output) return output @@ -123,11 +129,11 @@ class HtmlMinNode(template.Node): def render_markdown(context, markdown_text): # naive approach to reusing the renderer for a single request # works for bookmark list for now - if not ('markdown_renderer' in context): - renderer = markdown.Markdown(extensions=['fenced_code', 'nl2br']) - context['markdown_renderer'] = renderer + if not ("markdown_renderer" in context): + renderer = markdown.Markdown(extensions=["fenced_code", "nl2br"]) + context["markdown_renderer"] = renderer else: - renderer = context['markdown_renderer'] + renderer = context["markdown_renderer"] as_html = renderer.convert(markdown_text) sanitized_html = bleach.clean(as_html, markdown_tags, markdown_attrs) diff --git a/bookmarks/tests/helpers.py b/bookmarks/tests/helpers.py index 2fdfa86..069be5f 100644 --- a/bookmarks/tests/helpers.py +++ b/bookmarks/tests/helpers.py @@ -18,26 +18,29 @@ class BookmarkFactoryMixin: def get_or_create_test_user(self): if self.user is None: - self.user = User.objects.create_user('testuser', 'test@example.com', 'password123') + self.user = User.objects.create_user( + "testuser", "test@example.com", "password123" + ) return self.user - def setup_bookmark(self, - is_archived: bool = False, - unread: bool = False, - shared: bool = False, - tags=None, - user: User = None, - url: str = '', - title: str = None, - description: str = '', - notes: str = '', - website_title: str = '', - website_description: str = '', - web_archive_snapshot_url: str = '', - favicon_file: str = '', - added: datetime = None, - ): + def setup_bookmark( + self, + is_archived: bool = False, + unread: bool = False, + shared: bool = False, + tags=None, + user: User = None, + url: str = "", + title: str = None, + description: str = "", + notes: str = "", + website_title: str = "", + website_description: str = "", + web_archive_snapshot_url: str = "", + favicon_file: str = "", + added: datetime = None, + ): if title is None: title = get_random_string(length=32) if tags is None: @@ -46,7 +49,7 @@ class BookmarkFactoryMixin: user = self.get_or_create_test_user() if not url: unique_id = get_random_string(length=32) - url = 'https://example.com/' + unique_id + url = "https://example.com/" + unique_id if added is None: added = timezone.now() bookmark = Bookmark( @@ -71,49 +74,53 @@ class BookmarkFactoryMixin: bookmark.save() return bookmark - def setup_numbered_bookmarks(self, - count: int, - prefix: str = '', - suffix: str = '', - tag_prefix: str = '', - archived: bool = False, - unread: bool = False, - shared: bool = False, - with_tags: bool = False, - user: User = None): + def setup_numbered_bookmarks( + self, + count: int, + prefix: str = "", + suffix: str = "", + tag_prefix: str = "", + archived: bool = False, + unread: bool = False, + shared: bool = False, + with_tags: bool = False, + user: User = None, + ): user = user or self.get_or_create_test_user() bookmarks = [] if not prefix: if archived: - prefix = 'Archived Bookmark' + prefix = "Archived Bookmark" elif shared: - prefix = 'Shared Bookmark' + prefix = "Shared Bookmark" else: - prefix = 'Bookmark' + prefix = "Bookmark" if not tag_prefix: if archived: - tag_prefix = 'Archived Tag' + tag_prefix = "Archived Tag" elif shared: - tag_prefix = 'Shared Tag' + tag_prefix = "Shared Tag" else: - tag_prefix = 'Tag' + tag_prefix = "Tag" for i in range(1, count + 1): - title = f'{prefix} {i}{suffix}' - url = f'https://example.com/{prefix}/{i}' + title = f"{prefix} {i}{suffix}" + url = f"https://example.com/{prefix}/{i}" tags = [] if with_tags: - tag_name = f'{tag_prefix} {i}{suffix}' + tag_name = f"{tag_prefix} {i}{suffix}" tags = [self.setup_tag(name=tag_name, user=user)] - bookmark = self.setup_bookmark(url=url, - title=title, - is_archived=archived, - unread=unread, - shared=shared, - tags=tags, - user=user) + bookmark = self.setup_bookmark( + url=url, + title=title, + is_archived=archived, + unread=unread, + shared=shared, + tags=tags, + user=user, + ) bookmarks.append(bookmark) return bookmarks @@ -121,7 +128,7 @@ class BookmarkFactoryMixin: def get_numbered_bookmark(self, title: str): return Bookmark.objects.get(title=title) - def setup_tag(self, user: User = None, name: str = ''): + def setup_tag(self, user: User = None, name: str = ""): if user is None: user = self.get_or_create_test_user() if not name: @@ -130,10 +137,15 @@ class BookmarkFactoryMixin: tag.save() return tag - def setup_user(self, name: str = None, enable_sharing: bool = False, enable_public_sharing: bool = False): + def setup_user( + self, + name: str = None, + enable_sharing: bool = False, + enable_public_sharing: bool = False, + ): if not name: name = get_random_string(length=32) - user = User.objects.create_user(name, 'user@example.com', 'password123') + user = User.objects.create_user(name, "user@example.com", "password123") user.profile.enable_sharing = enable_sharing user.profile.enable_public_sharing = enable_public_sharing user.profile.save() @@ -161,17 +173,17 @@ class LinkdingApiTestCase(APITestCase): return response def post(self, url, data=None, expected_status_code=status.HTTP_200_OK): - response = self.client.post(url, data, format='json') + response = self.client.post(url, data, format="json") self.assertEqual(response.status_code, expected_status_code) return response def put(self, url, data=None, expected_status_code=status.HTTP_200_OK): - response = self.client.put(url, data, format='json') + response = self.client.put(url, data, format="json") self.assertEqual(response.status_code, expected_status_code) return response def patch(self, url, data=None, expected_status_code=status.HTTP_200_OK): - response = self.client.patch(url, data, format='json') + response = self.client.patch(url, data, format="json") self.assertEqual(response.status_code, expected_status_code) return response @@ -182,14 +194,16 @@ class LinkdingApiTestCase(APITestCase): class BookmarkHtmlTag: - def __init__(self, - href: str = '', - title: str = '', - description: str = '', - add_date: str = '', - tags: str = '', - to_read: bool = False, - private: bool = True): + def __init__( + self, + href: str = "", + title: str = "", + description: str = "", + add_date: str = "", + tags: str = "", + to_read: bool = False, + private: bool = True, + ): self.href = href self.title = title self.description = description @@ -201,7 +215,7 @@ class BookmarkHtmlTag: class ImportTestMixin: def render_tag(self, tag: BookmarkHtmlTag): - return f''' + return f"""

{f'
{tag.description}' if tag.description else ''} - ''' + """ - def render_html(self, tags: List[BookmarkHtmlTag] = None, tags_html: str = ''): + def render_html(self, tags: List[BookmarkHtmlTag] = None, tags_html: str = ""): if tags: rendered_tags = [self.render_tag(tag) for tag in tags] - tags_html = '\n'.join(rendered_tags) - return f''' + tags_html = "\n".join(rendered_tags) + return f""" Bookmarks @@ -225,34 +239,34 @@ class ImportTestMixin:

{tags_html}

- ''' + """ _words = [ - 'quasi', - 'consequatur', - 'necessitatibus', - 'debitis', - 'quod', - 'vero', - 'qui', - 'commodi', - 'quod', - 'odio', - 'aliquam', - 'veniam', - 'architecto', - 'consequatur', - 'autem', - 'qui', - 'iste', - 'asperiores', - 'soluta', - 'et', + "quasi", + "consequatur", + "necessitatibus", + "debitis", + "quod", + "vero", + "qui", + "commodi", + "quod", + "odio", + "aliquam", + "veniam", + "architecto", + "consequatur", + "autem", + "qui", + "iste", + "asperiores", + "soluta", + "et", ] -def random_sentence(num_words: int = None, including_word: str = ''): +def random_sentence(num_words: int = None, including_word: str = ""): if num_words is None: num_words = random.randint(5, 10) selected_words = random.choices(_words, k=num_words) @@ -260,7 +274,7 @@ def random_sentence(num_words: int = None, including_word: str = ''): selected_words.append(including_word) random.shuffle(selected_words) - return ' '.join(selected_words) + return " ".join(selected_words) def disable_logging(f): @@ -275,5 +289,5 @@ def disable_logging(f): def collapse_whitespace(text: str): - text = text.replace('\n', '').replace('\r', '') - return ' '.join(text.split()) + text = text.replace("\n", "").replace("\r", "") + return " ".join(text.split()) diff --git a/bookmarks/tests/test_anonymous_view.py b/bookmarks/tests/test_anonymous_view.py index 16ea3d4..8b8515f 100644 --- a/bookmarks/tests/test_anonymous_view.py +++ b/bookmarks/tests/test_anonymous_view.py @@ -6,21 +6,24 @@ from bookmarks.tests.helpers import BookmarkFactoryMixin class AnonymousViewTestCase(TestCase, BookmarkFactoryMixin): def assertSharedBookmarksLinkCount(self, response, count): - url = reverse('bookmarks:shared') - self.assertContains(response, f'Shared bookmarks', - count=count) + url = reverse("bookmarks:shared") + self.assertContains( + response, + f'Shared bookmarks', + count=count, + ) def test_publicly_shared_bookmarks_link(self): # should not render link if no public shares exist user = self.setup_user(enable_sharing=True) self.setup_bookmark(user=user, shared=True) - response = self.client.get(reverse('login')) + response = self.client.get(reverse("login")) self.assertSharedBookmarksLinkCount(response, 0) # should render link if public shares exist user.profile.enable_public_sharing = True user.profile.save() - response = self.client.get(reverse('login')) + response = self.client.get(reverse("login")) self.assertSharedBookmarksLinkCount(response, 1) diff --git a/bookmarks/tests/test_app_options.py b/bookmarks/tests/test_app_options.py index f359184..391b023 100644 --- a/bookmarks/tests/test_app_options.py +++ b/bookmarks/tests/test_app_options.py @@ -7,23 +7,35 @@ from django.test import TestCase class AppOptionsTestCase(TestCase): def setUp(self) -> None: - self.settings_module = importlib.import_module('siteroot.settings.base') + self.settings_module = importlib.import_module("siteroot.settings.base") def test_empty_csrf_trusted_origins(self): module = importlib.reload(self.settings_module) - self.assertFalse(hasattr(module, 'CSRF_TRUSTED_ORIGINS')) + self.assertFalse(hasattr(module, "CSRF_TRUSTED_ORIGINS")) - @mock.patch.dict(os.environ, {'LD_CSRF_TRUSTED_ORIGINS': 'https://linkding.example.com'}) + @mock.patch.dict( + os.environ, {"LD_CSRF_TRUSTED_ORIGINS": "https://linkding.example.com"} + ) def test_single_csrf_trusted_origin(self): module = importlib.reload(self.settings_module) - self.assertTrue(hasattr(module, 'CSRF_TRUSTED_ORIGINS')) - self.assertCountEqual(module.CSRF_TRUSTED_ORIGINS, ['https://linkding.example.com']) + self.assertTrue(hasattr(module, "CSRF_TRUSTED_ORIGINS")) + self.assertCountEqual( + module.CSRF_TRUSTED_ORIGINS, ["https://linkding.example.com"] + ) - @mock.patch.dict(os.environ, {'LD_CSRF_TRUSTED_ORIGINS': 'https://linkding.example.com,http://linkding.example.com'}) + @mock.patch.dict( + os.environ, + { + "LD_CSRF_TRUSTED_ORIGINS": "https://linkding.example.com,http://linkding.example.com" + }, + ) def test_multiple_csrf_trusted_origin(self): module = importlib.reload(self.settings_module) - self.assertTrue(hasattr(module, 'CSRF_TRUSTED_ORIGINS')) - self.assertCountEqual(module.CSRF_TRUSTED_ORIGINS, ['https://linkding.example.com', 'http://linkding.example.com']) + self.assertTrue(hasattr(module, "CSRF_TRUSTED_ORIGINS")) + self.assertCountEqual( + module.CSRF_TRUSTED_ORIGINS, + ["https://linkding.example.com", "http://linkding.example.com"], + ) diff --git a/bookmarks/tests/test_auth_proxy_support.py b/bookmarks/tests/test_auth_proxy_support.py index 392119d..526c866 100644 --- a/bookmarks/tests/test_auth_proxy_support.py +++ b/bookmarks/tests/test_auth_proxy_support.py @@ -10,37 +10,49 @@ class AuthProxySupportTest(TestCase): # Reproducing configuration from the settings logic here # ideally this test would just override the respective options @modify_settings( - MIDDLEWARE={'append': 'bookmarks.middlewares.CustomRemoteUserMiddleware'}, - AUTHENTICATION_BACKENDS={'prepend': 'django.contrib.auth.backends.RemoteUserBackend'} + MIDDLEWARE={"append": "bookmarks.middlewares.CustomRemoteUserMiddleware"}, + AUTHENTICATION_BACKENDS={ + "prepend": "django.contrib.auth.backends.RemoteUserBackend" + }, ) def test_auth_proxy_authentication(self): - user = User.objects.create_user('auth_proxy_user', 'user@example.com', 'password123') + user = User.objects.create_user( + "auth_proxy_user", "user@example.com", "password123" + ) - headers = {'REMOTE_USER': user.username} - response = self.client.get(reverse('bookmarks:index'), **headers) + headers = {"REMOTE_USER": user.username} + response = self.client.get(reverse("bookmarks:index"), **headers) self.assertEqual(response.status_code, 200) # Reproducing configuration from the settings logic here # ideally this test would just override the respective options @modify_settings( - MIDDLEWARE={'append': 'bookmarks.middlewares.CustomRemoteUserMiddleware'}, - AUTHENTICATION_BACKENDS={'prepend': 'django.contrib.auth.backends.RemoteUserBackend'} + MIDDLEWARE={"append": "bookmarks.middlewares.CustomRemoteUserMiddleware"}, + AUTHENTICATION_BACKENDS={ + "prepend": "django.contrib.auth.backends.RemoteUserBackend" + }, ) def test_auth_proxy_with_custom_header(self): - with patch.object(CustomRemoteUserMiddleware, 'header', new_callable=PropertyMock) as mock: - mock.return_value = 'Custom-User' - user = User.objects.create_user('auth_proxy_user', 'user@example.com', 'password123') + with patch.object( + CustomRemoteUserMiddleware, "header", new_callable=PropertyMock + ) as mock: + mock.return_value = "Custom-User" + user = User.objects.create_user( + "auth_proxy_user", "user@example.com", "password123" + ) - headers = {'Custom-User': user.username} - response = self.client.get(reverse('bookmarks:index'), **headers) + headers = {"Custom-User": user.username} + response = self.client.get(reverse("bookmarks:index"), **headers) self.assertEqual(response.status_code, 200) def test_auth_proxy_is_disabled_by_default(self): - user = User.objects.create_user('auth_proxy_user', 'user@example.com', 'password123') + user = User.objects.create_user( + "auth_proxy_user", "user@example.com", "password123" + ) - headers = {'REMOTE_USER': user.username} - response = self.client.get(reverse('bookmarks:index'), **headers, follow=True) + headers = {"REMOTE_USER": user.username} + response = self.client.get(reverse("bookmarks:index"), **headers, follow=True) - self.assertRedirects(response, '/login/?next=%2Fbookmarks') + self.assertRedirects(response, "/login/?next=%2Fbookmarks") diff --git a/bookmarks/tests/test_bookmark_action_view.py b/bookmarks/tests/test_bookmark_action_view.py index ca5fe69..658cb5a 100644 --- a/bookmarks/tests/test_bookmark_action_view.py +++ b/bookmarks/tests/test_bookmark_action_view.py @@ -17,26 +17,37 @@ class BookmarkActionViewTestCase(TestCase, BookmarkFactoryMixin): self.assertEqual(len(bookmarks), Bookmark.objects.count()) for bookmark in bookmarks: - self.assertEqual(model_to_dict(bookmark), model_to_dict(Bookmark.objects.get(id=bookmark.id))) + self.assertEqual( + model_to_dict(bookmark), + model_to_dict(Bookmark.objects.get(id=bookmark.id)), + ) def test_archive_should_archive_bookmark(self): bookmark = self.setup_bookmark() - self.client.post(reverse('bookmarks:index.action'), { - 'archive': [bookmark.id], - }) + self.client.post( + reverse("bookmarks:index.action"), + { + "archive": [bookmark.id], + }, + ) bookmark.refresh_from_db() self.assertTrue(bookmark.is_archived) def test_can_only_archive_own_bookmarks(self): - other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123') + other_user = User.objects.create_user( + "otheruser", "otheruser@example.com", "password123" + ) bookmark = self.setup_bookmark(user=other_user) - response = self.client.post(reverse('bookmarks:index.action'), { - 'archive': [bookmark.id], - }) + response = self.client.post( + reverse("bookmarks:index.action"), + { + "archive": [bookmark.id], + }, + ) bookmark.refresh_from_db() @@ -46,20 +57,28 @@ class BookmarkActionViewTestCase(TestCase, BookmarkFactoryMixin): def test_unarchive_should_unarchive_bookmark(self): bookmark = self.setup_bookmark(is_archived=True) - self.client.post(reverse('bookmarks:index.action'), { - 'unarchive': [bookmark.id], - }) + self.client.post( + reverse("bookmarks:index.action"), + { + "unarchive": [bookmark.id], + }, + ) bookmark.refresh_from_db() self.assertFalse(bookmark.is_archived) def test_unarchive_can_only_archive_own_bookmarks(self): - other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123') + other_user = User.objects.create_user( + "otheruser", "otheruser@example.com", "password123" + ) bookmark = self.setup_bookmark(is_archived=True, user=other_user) - response = self.client.post(reverse('bookmarks:index.action'), { - 'unarchive': [bookmark.id], - }) + response = self.client.post( + reverse("bookmarks:index.action"), + { + "unarchive": [bookmark.id], + }, + ) bookmark.refresh_from_db() self.assertEqual(response.status_code, 404) @@ -68,28 +87,39 @@ class BookmarkActionViewTestCase(TestCase, BookmarkFactoryMixin): def test_delete_should_delete_bookmark(self): bookmark = self.setup_bookmark() - self.client.post(reverse('bookmarks:index.action'), { - 'remove': [bookmark.id], - }) + self.client.post( + reverse("bookmarks:index.action"), + { + "remove": [bookmark.id], + }, + ) self.assertEqual(Bookmark.objects.count(), 0) def test_delete_can_only_delete_own_bookmarks(self): - other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123') + other_user = User.objects.create_user( + "otheruser", "otheruser@example.com", "password123" + ) bookmark = self.setup_bookmark(user=other_user) - response = self.client.post(reverse('bookmarks:index.action'), { - 'remove': [bookmark.id], - }) + response = self.client.post( + reverse("bookmarks:index.action"), + { + "remove": [bookmark.id], + }, + ) self.assertEqual(response.status_code, 404) self.assertTrue(Bookmark.objects.filter(id=bookmark.id).exists()) def test_mark_as_read(self): bookmark = self.setup_bookmark(unread=True) - self.client.post(reverse('bookmarks:index.action'), { - 'mark_as_read': [bookmark.id], - }) + self.client.post( + reverse("bookmarks:index.action"), + { + "mark_as_read": [bookmark.id], + }, + ) bookmark.refresh_from_db() self.assertFalse(bookmark.unread) @@ -97,21 +127,29 @@ class BookmarkActionViewTestCase(TestCase, BookmarkFactoryMixin): def test_unshare_should_unshare_bookmark(self): bookmark = self.setup_bookmark(shared=True) - self.client.post(reverse('bookmarks:index.action'), { - 'unshare': [bookmark.id], - }) + self.client.post( + reverse("bookmarks:index.action"), + { + "unshare": [bookmark.id], + }, + ) bookmark.refresh_from_db() self.assertFalse(bookmark.shared) def test_can_only_unshare_own_bookmarks(self): - other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123') + other_user = User.objects.create_user( + "otheruser", "otheruser@example.com", "password123" + ) bookmark = self.setup_bookmark(user=other_user, shared=True) - response = self.client.post(reverse('bookmarks:index.action'), { - 'unshare': [bookmark.id], - }) + response = self.client.post( + reverse("bookmarks:index.action"), + { + "unshare": [bookmark.id], + }, + ) bookmark.refresh_from_db() @@ -123,27 +161,43 @@ class BookmarkActionViewTestCase(TestCase, BookmarkFactoryMixin): bookmark2 = self.setup_bookmark() bookmark3 = self.setup_bookmark() - self.client.post(reverse('bookmarks:index.action'), { - 'bulk_action': ['bulk_archive'], - 'bulk_execute': [''], - 'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)], - }) + self.client.post( + reverse("bookmarks:index.action"), + { + "bulk_action": ["bulk_archive"], + "bulk_execute": [""], + "bookmark_id": [ + str(bookmark1.id), + str(bookmark2.id), + str(bookmark3.id), + ], + }, + ) self.assertTrue(Bookmark.objects.get(id=bookmark1.id).is_archived) self.assertTrue(Bookmark.objects.get(id=bookmark2.id).is_archived) self.assertTrue(Bookmark.objects.get(id=bookmark3.id).is_archived) def test_can_only_bulk_archive_own_bookmarks(self): - other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123') + other_user = User.objects.create_user( + "otheruser", "otheruser@example.com", "password123" + ) bookmark1 = self.setup_bookmark(user=other_user) bookmark2 = self.setup_bookmark(user=other_user) bookmark3 = self.setup_bookmark(user=other_user) - self.client.post(reverse('bookmarks:index.action'), { - 'bulk_action': ['bulk_archive'], - 'bulk_execute': [''], - 'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)], - }) + self.client.post( + reverse("bookmarks:index.action"), + { + "bulk_action": ["bulk_archive"], + "bulk_execute": [""], + "bookmark_id": [ + str(bookmark1.id), + str(bookmark2.id), + str(bookmark3.id), + ], + }, + ) self.assertFalse(Bookmark.objects.get(id=bookmark1.id).is_archived) self.assertFalse(Bookmark.objects.get(id=bookmark2.id).is_archived) @@ -154,27 +208,43 @@ class BookmarkActionViewTestCase(TestCase, BookmarkFactoryMixin): bookmark2 = self.setup_bookmark(is_archived=True) bookmark3 = self.setup_bookmark(is_archived=True) - self.client.post(reverse('bookmarks:archived.action'), { - 'bulk_action': ['bulk_unarchive'], - 'bulk_execute': [''], - 'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)], - }) + self.client.post( + reverse("bookmarks:archived.action"), + { + "bulk_action": ["bulk_unarchive"], + "bulk_execute": [""], + "bookmark_id": [ + str(bookmark1.id), + str(bookmark2.id), + str(bookmark3.id), + ], + }, + ) self.assertFalse(Bookmark.objects.get(id=bookmark1.id).is_archived) self.assertFalse(Bookmark.objects.get(id=bookmark2.id).is_archived) self.assertFalse(Bookmark.objects.get(id=bookmark3.id).is_archived) def test_can_only_bulk_unarchive_own_bookmarks(self): - other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123') + other_user = User.objects.create_user( + "otheruser", "otheruser@example.com", "password123" + ) bookmark1 = self.setup_bookmark(is_archived=True, user=other_user) bookmark2 = self.setup_bookmark(is_archived=True, user=other_user) bookmark3 = self.setup_bookmark(is_archived=True, user=other_user) - self.client.post(reverse('bookmarks:archived.action'), { - 'bulk_action': ['bulk_unarchive'], - 'bulk_execute': [''], - 'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)], - }) + self.client.post( + reverse("bookmarks:archived.action"), + { + "bulk_action": ["bulk_unarchive"], + "bulk_execute": [""], + "bookmark_id": [ + str(bookmark1.id), + str(bookmark2.id), + str(bookmark3.id), + ], + }, + ) self.assertTrue(Bookmark.objects.get(id=bookmark1.id).is_archived) self.assertTrue(Bookmark.objects.get(id=bookmark2.id).is_archived) @@ -185,27 +255,43 @@ class BookmarkActionViewTestCase(TestCase, BookmarkFactoryMixin): bookmark2 = self.setup_bookmark() bookmark3 = self.setup_bookmark() - self.client.post(reverse('bookmarks:index.action'), { - 'bulk_action': ['bulk_delete'], - 'bulk_execute': [''], - 'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)], - }) + self.client.post( + reverse("bookmarks:index.action"), + { + "bulk_action": ["bulk_delete"], + "bulk_execute": [""], + "bookmark_id": [ + str(bookmark1.id), + str(bookmark2.id), + str(bookmark3.id), + ], + }, + ) self.assertIsNone(Bookmark.objects.filter(id=bookmark1.id).first()) self.assertIsNone(Bookmark.objects.filter(id=bookmark2.id).first()) self.assertIsNone(Bookmark.objects.filter(id=bookmark3.id).first()) def test_can_only_bulk_delete_own_bookmarks(self): - other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123') + other_user = User.objects.create_user( + "otheruser", "otheruser@example.com", "password123" + ) bookmark1 = self.setup_bookmark(user=other_user) bookmark2 = self.setup_bookmark(user=other_user) bookmark3 = self.setup_bookmark(user=other_user) - self.client.post(reverse('bookmarks:index.action'), { - 'bulk_action': ['bulk_delete'], - 'bulk_execute': [''], - 'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)], - }) + self.client.post( + reverse("bookmarks:index.action"), + { + "bulk_action": ["bulk_delete"], + "bulk_execute": [""], + "bookmark_id": [ + str(bookmark1.id), + str(bookmark2.id), + str(bookmark3.id), + ], + }, + ) self.assertIsNotNone(Bookmark.objects.filter(id=bookmark1.id).first()) self.assertIsNotNone(Bookmark.objects.filter(id=bookmark2.id).first()) @@ -218,12 +304,19 @@ class BookmarkActionViewTestCase(TestCase, BookmarkFactoryMixin): tag1 = self.setup_tag() tag2 = self.setup_tag() - self.client.post(reverse('bookmarks:index.action'), { - 'bulk_action': ['bulk_tag'], - 'bulk_execute': [''], - 'bulk_tag_string': [f'{tag1.name} {tag2.name}'], - 'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)], - }) + self.client.post( + reverse("bookmarks:index.action"), + { + "bulk_action": ["bulk_tag"], + "bulk_execute": [""], + "bulk_tag_string": [f"{tag1.name} {tag2.name}"], + "bookmark_id": [ + str(bookmark1.id), + str(bookmark2.id), + str(bookmark3.id), + ], + }, + ) bookmark1.refresh_from_db() bookmark2.refresh_from_db() @@ -234,19 +327,28 @@ class BookmarkActionViewTestCase(TestCase, BookmarkFactoryMixin): self.assertCountEqual(bookmark3.tags.all(), [tag1, tag2]) def test_can_only_bulk_tag_own_bookmarks(self): - other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123') + other_user = User.objects.create_user( + "otheruser", "otheruser@example.com", "password123" + ) bookmark1 = self.setup_bookmark(user=other_user) bookmark2 = self.setup_bookmark(user=other_user) bookmark3 = self.setup_bookmark(user=other_user) tag1 = self.setup_tag() tag2 = self.setup_tag() - self.client.post(reverse('bookmarks:index.action'), { - 'bulk_action': ['bulk_tag'], - 'bulk_execute': [''], - 'bulk_tag_string': [f'{tag1.name} {tag2.name}'], - 'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)], - }) + self.client.post( + reverse("bookmarks:index.action"), + { + "bulk_action": ["bulk_tag"], + "bulk_execute": [""], + "bulk_tag_string": [f"{tag1.name} {tag2.name}"], + "bookmark_id": [ + str(bookmark1.id), + str(bookmark2.id), + str(bookmark3.id), + ], + }, + ) bookmark1.refresh_from_db() bookmark2.refresh_from_db() @@ -263,12 +365,19 @@ class BookmarkActionViewTestCase(TestCase, BookmarkFactoryMixin): bookmark2 = self.setup_bookmark(tags=[tag1, tag2]) bookmark3 = self.setup_bookmark(tags=[tag1, tag2]) - self.client.post(reverse('bookmarks:index.action'), { - 'bulk_action': ['bulk_untag'], - 'bulk_execute': [''], - 'bulk_tag_string': [f'{tag1.name} {tag2.name}'], - 'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)], - }) + self.client.post( + reverse("bookmarks:index.action"), + { + "bulk_action": ["bulk_untag"], + "bulk_execute": [""], + "bulk_tag_string": [f"{tag1.name} {tag2.name}"], + "bookmark_id": [ + str(bookmark1.id), + str(bookmark2.id), + str(bookmark3.id), + ], + }, + ) bookmark1.refresh_from_db() bookmark2.refresh_from_db() @@ -279,19 +388,28 @@ class BookmarkActionViewTestCase(TestCase, BookmarkFactoryMixin): self.assertCountEqual(bookmark3.tags.all(), []) def test_can_only_bulk_untag_own_bookmarks(self): - other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123') + other_user = User.objects.create_user( + "otheruser", "otheruser@example.com", "password123" + ) tag1 = self.setup_tag() tag2 = self.setup_tag() bookmark1 = self.setup_bookmark(tags=[tag1, tag2], user=other_user) bookmark2 = self.setup_bookmark(tags=[tag1, tag2], user=other_user) bookmark3 = self.setup_bookmark(tags=[tag1, tag2], user=other_user) - self.client.post(reverse('bookmarks:index.action'), { - 'bulk_action': ['bulk_untag'], - 'bulk_execute': [''], - 'bulk_tag_string': [f'{tag1.name} {tag2.name}'], - 'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)], - }) + self.client.post( + reverse("bookmarks:index.action"), + { + "bulk_action": ["bulk_untag"], + "bulk_execute": [""], + "bulk_tag_string": [f"{tag1.name} {tag2.name}"], + "bookmark_id": [ + str(bookmark1.id), + str(bookmark2.id), + str(bookmark3.id), + ], + }, + ) bookmark1.refresh_from_db() bookmark2.refresh_from_db() @@ -306,27 +424,43 @@ class BookmarkActionViewTestCase(TestCase, BookmarkFactoryMixin): bookmark2 = self.setup_bookmark(unread=True) bookmark3 = self.setup_bookmark(unread=True) - self.client.post(reverse('bookmarks:index.action'), { - 'bulk_action': ['bulk_read'], - 'bulk_execute': [''], - 'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)], - }) + self.client.post( + reverse("bookmarks:index.action"), + { + "bulk_action": ["bulk_read"], + "bulk_execute": [""], + "bookmark_id": [ + str(bookmark1.id), + str(bookmark2.id), + str(bookmark3.id), + ], + }, + ) self.assertFalse(Bookmark.objects.get(id=bookmark1.id).unread) self.assertFalse(Bookmark.objects.get(id=bookmark2.id).unread) self.assertFalse(Bookmark.objects.get(id=bookmark3.id).unread) def test_can_only_bulk_mark_as_read_own_bookmarks(self): - other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123') + other_user = User.objects.create_user( + "otheruser", "otheruser@example.com", "password123" + ) bookmark1 = self.setup_bookmark(unread=True, user=other_user) bookmark2 = self.setup_bookmark(unread=True, user=other_user) bookmark3 = self.setup_bookmark(unread=True, user=other_user) - self.client.post(reverse('bookmarks:index.action'), { - 'bulk_action': ['bulk_read'], - 'bulk_execute': [''], - 'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)], - }) + self.client.post( + reverse("bookmarks:index.action"), + { + "bulk_action": ["bulk_read"], + "bulk_execute": [""], + "bookmark_id": [ + str(bookmark1.id), + str(bookmark2.id), + str(bookmark3.id), + ], + }, + ) self.assertTrue(Bookmark.objects.get(id=bookmark1.id).unread) self.assertTrue(Bookmark.objects.get(id=bookmark2.id).unread) @@ -337,27 +471,43 @@ class BookmarkActionViewTestCase(TestCase, BookmarkFactoryMixin): bookmark2 = self.setup_bookmark(unread=False) bookmark3 = self.setup_bookmark(unread=False) - self.client.post(reverse('bookmarks:index.action'), { - 'bulk_action': ['bulk_unread'], - 'bulk_execute': [''], - 'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)], - }) + self.client.post( + reverse("bookmarks:index.action"), + { + "bulk_action": ["bulk_unread"], + "bulk_execute": [""], + "bookmark_id": [ + str(bookmark1.id), + str(bookmark2.id), + str(bookmark3.id), + ], + }, + ) self.assertTrue(Bookmark.objects.get(id=bookmark1.id).unread) self.assertTrue(Bookmark.objects.get(id=bookmark2.id).unread) self.assertTrue(Bookmark.objects.get(id=bookmark3.id).unread) def test_can_only_bulk_mark_as_unread_own_bookmarks(self): - other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123') + other_user = User.objects.create_user( + "otheruser", "otheruser@example.com", "password123" + ) bookmark1 = self.setup_bookmark(unread=False, user=other_user) bookmark2 = self.setup_bookmark(unread=False, user=other_user) bookmark3 = self.setup_bookmark(unread=False, user=other_user) - self.client.post(reverse('bookmarks:index.action'), { - 'bulk_action': ['bulk_unread'], - 'bulk_execute': [''], - 'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)], - }) + self.client.post( + reverse("bookmarks:index.action"), + { + "bulk_action": ["bulk_unread"], + "bulk_execute": [""], + "bookmark_id": [ + str(bookmark1.id), + str(bookmark2.id), + str(bookmark3.id), + ], + }, + ) self.assertFalse(Bookmark.objects.get(id=bookmark1.id).unread) self.assertFalse(Bookmark.objects.get(id=bookmark2.id).unread) @@ -368,27 +518,43 @@ class BookmarkActionViewTestCase(TestCase, BookmarkFactoryMixin): bookmark2 = self.setup_bookmark(shared=False) bookmark3 = self.setup_bookmark(shared=False) - self.client.post(reverse('bookmarks:index.action'), { - 'bulk_action': ['bulk_share'], - 'bulk_execute': [''], - 'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)], - }) + self.client.post( + reverse("bookmarks:index.action"), + { + "bulk_action": ["bulk_share"], + "bulk_execute": [""], + "bookmark_id": [ + str(bookmark1.id), + str(bookmark2.id), + str(bookmark3.id), + ], + }, + ) self.assertTrue(Bookmark.objects.get(id=bookmark1.id).shared) self.assertTrue(Bookmark.objects.get(id=bookmark2.id).shared) self.assertTrue(Bookmark.objects.get(id=bookmark3.id).shared) def test_can_only_bulk_share_own_bookmarks(self): - other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123') + other_user = User.objects.create_user( + "otheruser", "otheruser@example.com", "password123" + ) bookmark1 = self.setup_bookmark(shared=False, user=other_user) bookmark2 = self.setup_bookmark(shared=False, user=other_user) bookmark3 = self.setup_bookmark(shared=False, user=other_user) - self.client.post(reverse('bookmarks:index.action'), { - 'bulk_action': ['bulk_share'], - 'bulk_execute': [''], - 'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)], - }) + self.client.post( + reverse("bookmarks:index.action"), + { + "bulk_action": ["bulk_share"], + "bulk_execute": [""], + "bookmark_id": [ + str(bookmark1.id), + str(bookmark2.id), + str(bookmark3.id), + ], + }, + ) self.assertFalse(Bookmark.objects.get(id=bookmark1.id).shared) self.assertFalse(Bookmark.objects.get(id=bookmark2.id).shared) @@ -399,27 +565,43 @@ class BookmarkActionViewTestCase(TestCase, BookmarkFactoryMixin): bookmark2 = self.setup_bookmark(shared=True) bookmark3 = self.setup_bookmark(shared=True) - self.client.post(reverse('bookmarks:index.action'), { - 'bulk_action': ['bulk_unshare'], - 'bulk_execute': [''], - 'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)], - }) + self.client.post( + reverse("bookmarks:index.action"), + { + "bulk_action": ["bulk_unshare"], + "bulk_execute": [""], + "bookmark_id": [ + str(bookmark1.id), + str(bookmark2.id), + str(bookmark3.id), + ], + }, + ) self.assertFalse(Bookmark.objects.get(id=bookmark1.id).shared) self.assertFalse(Bookmark.objects.get(id=bookmark2.id).shared) self.assertFalse(Bookmark.objects.get(id=bookmark3.id).shared) def test_can_only_bulk_unshare_own_bookmarks(self): - other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123') + other_user = User.objects.create_user( + "otheruser", "otheruser@example.com", "password123" + ) bookmark1 = self.setup_bookmark(shared=True, user=other_user) bookmark2 = self.setup_bookmark(shared=True, user=other_user) bookmark3 = self.setup_bookmark(shared=True, user=other_user) - self.client.post(reverse('bookmarks:index.action'), { - 'bulk_action': ['bulk_unshare'], - 'bulk_execute': [''], - 'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)], - }) + self.client.post( + reverse("bookmarks:index.action"), + { + "bulk_action": ["bulk_unshare"], + "bulk_execute": [""], + "bookmark_id": [ + str(bookmark1.id), + str(bookmark2.id), + str(bookmark3.id), + ], + }, + ) self.assertTrue(Bookmark.objects.get(id=bookmark1.id).shared) self.assertTrue(Bookmark.objects.get(id=bookmark2.id).shared) @@ -430,11 +612,14 @@ class BookmarkActionViewTestCase(TestCase, BookmarkFactoryMixin): bookmark2 = self.setup_bookmark() bookmark3 = self.setup_bookmark() - self.client.post(reverse('bookmarks:index.action'), { - 'bulk_action': ['bulk_archive'], - 'bulk_execute': [''], - 'bulk_select_across': ['on'], - }) + self.client.post( + reverse("bookmarks:index.action"), + { + "bulk_action": ["bulk_archive"], + "bulk_execute": [""], + "bulk_select_across": ["on"], + }, + ) self.assertTrue(Bookmark.objects.get(id=bookmark1.id).is_archived) self.assertTrue(Bookmark.objects.get(id=bookmark2.id).is_archived) @@ -443,11 +628,14 @@ class BookmarkActionViewTestCase(TestCase, BookmarkFactoryMixin): def test_bulk_select_across_ignores_page(self): self.setup_numbered_bookmarks(100) - self.client.post(reverse('bookmarks:index.action') + '?page=2', { - 'bulk_action': ['bulk_delete'], - 'bulk_execute': [''], - 'bulk_select_across': ['on'], - }) + self.client.post( + reverse("bookmarks:index.action") + "?page=2", + { + "bulk_action": ["bulk_delete"], + "bulk_execute": [""], + "bulk_select_across": ["on"], + }, + ) self.assertEqual(0, Bookmark.objects.count()) @@ -455,85 +643,108 @@ class BookmarkActionViewTestCase(TestCase, BookmarkFactoryMixin): # create a number of bookmarks with different states / visibility self.setup_numbered_bookmarks(3, with_tags=True) self.setup_numbered_bookmarks(3, with_tags=True, archived=True) - self.setup_numbered_bookmarks(3, - shared=True, - prefix="Joe's Bookmark", - user=self.setup_user(enable_sharing=True)) + self.setup_numbered_bookmarks( + 3, + shared=True, + prefix="Joe's Bookmark", + user=self.setup_user(enable_sharing=True), + ) def test_index_action_bulk_select_across_only_affects_active_bookmarks(self): self.setup_bulk_edit_scope_test_data() - self.assertIsNotNone(Bookmark.objects.filter(title='Bookmark 1').first()) - self.assertIsNotNone(Bookmark.objects.filter(title='Bookmark 2').first()) - self.assertIsNotNone(Bookmark.objects.filter(title='Bookmark 3').first()) + self.assertIsNotNone(Bookmark.objects.filter(title="Bookmark 1").first()) + self.assertIsNotNone(Bookmark.objects.filter(title="Bookmark 2").first()) + self.assertIsNotNone(Bookmark.objects.filter(title="Bookmark 3").first()) - self.client.post(reverse('bookmarks:index.action'), { - 'bulk_action': ['bulk_delete'], - 'bulk_execute': [''], - 'bulk_select_across': ['on'], - }) + self.client.post( + reverse("bookmarks:index.action"), + { + "bulk_action": ["bulk_delete"], + "bulk_execute": [""], + "bulk_select_across": ["on"], + }, + ) self.assertEqual(6, Bookmark.objects.count()) - self.assertIsNone(Bookmark.objects.filter(title='Bookmark 1').first()) - self.assertIsNone(Bookmark.objects.filter(title='Bookmark 2').first()) - self.assertIsNone(Bookmark.objects.filter(title='Bookmark 3').first()) + self.assertIsNone(Bookmark.objects.filter(title="Bookmark 1").first()) + self.assertIsNone(Bookmark.objects.filter(title="Bookmark 2").first()) + self.assertIsNone(Bookmark.objects.filter(title="Bookmark 3").first()) def test_index_action_bulk_select_across_respects_query(self): - self.setup_numbered_bookmarks(3, prefix='foo') - self.setup_numbered_bookmarks(3, prefix='bar') + self.setup_numbered_bookmarks(3, prefix="foo") + self.setup_numbered_bookmarks(3, prefix="bar") - self.assertEqual(3, Bookmark.objects.filter(title__startswith='foo').count()) + self.assertEqual(3, Bookmark.objects.filter(title__startswith="foo").count()) - self.client.post(reverse('bookmarks:index.action') + '?q=foo', { - 'bulk_action': ['bulk_delete'], - 'bulk_execute': [''], - 'bulk_select_across': ['on'], - }) + self.client.post( + reverse("bookmarks:index.action") + "?q=foo", + { + "bulk_action": ["bulk_delete"], + "bulk_execute": [""], + "bulk_select_across": ["on"], + }, + ) - self.assertEqual(0, Bookmark.objects.filter(title__startswith='foo').count()) - self.assertEqual(3, Bookmark.objects.filter(title__startswith='bar').count()) + self.assertEqual(0, Bookmark.objects.filter(title__startswith="foo").count()) + self.assertEqual(3, Bookmark.objects.filter(title__startswith="bar").count()) def test_archived_action_bulk_select_across_only_affects_archived_bookmarks(self): self.setup_bulk_edit_scope_test_data() - self.assertIsNotNone(Bookmark.objects.filter(title='Archived Bookmark 1').first()) - self.assertIsNotNone(Bookmark.objects.filter(title='Archived Bookmark 2').first()) - self.assertIsNotNone(Bookmark.objects.filter(title='Archived Bookmark 3').first()) + self.assertIsNotNone( + Bookmark.objects.filter(title="Archived Bookmark 1").first() + ) + self.assertIsNotNone( + Bookmark.objects.filter(title="Archived Bookmark 2").first() + ) + self.assertIsNotNone( + Bookmark.objects.filter(title="Archived Bookmark 3").first() + ) - self.client.post(reverse('bookmarks:archived.action'), { - 'bulk_action': ['bulk_delete'], - 'bulk_execute': [''], - 'bulk_select_across': ['on'], - }) + self.client.post( + reverse("bookmarks:archived.action"), + { + "bulk_action": ["bulk_delete"], + "bulk_execute": [""], + "bulk_select_across": ["on"], + }, + ) self.assertEqual(6, Bookmark.objects.count()) - self.assertIsNone(Bookmark.objects.filter(title='Archived Bookmark 1').first()) - self.assertIsNone(Bookmark.objects.filter(title='Archived Bookmark 2').first()) - self.assertIsNone(Bookmark.objects.filter(title='Archived Bookmark 3').first()) + self.assertIsNone(Bookmark.objects.filter(title="Archived Bookmark 1").first()) + self.assertIsNone(Bookmark.objects.filter(title="Archived Bookmark 2").first()) + self.assertIsNone(Bookmark.objects.filter(title="Archived Bookmark 3").first()) def test_archived_action_bulk_select_across_respects_query(self): - self.setup_numbered_bookmarks(3, prefix='foo', archived=True) - self.setup_numbered_bookmarks(3, prefix='bar', archived=True) + self.setup_numbered_bookmarks(3, prefix="foo", archived=True) + self.setup_numbered_bookmarks(3, prefix="bar", archived=True) - self.assertEqual(3, Bookmark.objects.filter(title__startswith='foo').count()) + self.assertEqual(3, Bookmark.objects.filter(title__startswith="foo").count()) - self.client.post(reverse('bookmarks:archived.action') + '?q=foo', { - 'bulk_action': ['bulk_delete'], - 'bulk_execute': [''], - 'bulk_select_across': ['on'], - }) + self.client.post( + reverse("bookmarks:archived.action") + "?q=foo", + { + "bulk_action": ["bulk_delete"], + "bulk_execute": [""], + "bulk_select_across": ["on"], + }, + ) - self.assertEqual(0, Bookmark.objects.filter(title__startswith='foo').count()) - self.assertEqual(3, Bookmark.objects.filter(title__startswith='bar').count()) + self.assertEqual(0, Bookmark.objects.filter(title__startswith="foo").count()) + self.assertEqual(3, Bookmark.objects.filter(title__startswith="bar").count()) def test_shared_action_bulk_select_across_not_supported(self): self.setup_bulk_edit_scope_test_data() - response = self.client.post(reverse('bookmarks:shared.action'), { - 'bulk_action': ['bulk_delete'], - 'bulk_execute': [''], - 'bulk_select_across': ['on'], - }) + response = self.client.post( + reverse("bookmarks:shared.action"), + { + "bulk_action": ["bulk_delete"], + "bulk_execute": [""], + "bulk_select_across": ["on"], + }, + ) self.assertEqual(response.status_code, 400) def test_handles_empty_bookmark_id(self): @@ -541,17 +752,23 @@ class BookmarkActionViewTestCase(TestCase, BookmarkFactoryMixin): bookmark2 = self.setup_bookmark() bookmark3 = self.setup_bookmark() - response = self.client.post(reverse('bookmarks:index.action'), { - 'bulk_action': ['bulk_archive'], - 'bulk_execute': [''], - }) + response = self.client.post( + reverse("bookmarks:index.action"), + { + "bulk_action": ["bulk_archive"], + "bulk_execute": [""], + }, + ) self.assertEqual(response.status_code, 302) - response = self.client.post(reverse('bookmarks:index.action'), { - 'bulk_action': ['bulk_archive'], - 'bulk_execute': [''], - 'bookmark_id': [], - }) + response = self.client.post( + reverse("bookmarks:index.action"), + { + "bulk_action": ["bulk_archive"], + "bulk_execute": [""], + "bookmark_id": [], + }, + ) self.assertEqual(response.status_code, 302) self.assertBookmarksAreUnmodified([bookmark1, bookmark2, bookmark3]) @@ -561,9 +778,16 @@ class BookmarkActionViewTestCase(TestCase, BookmarkFactoryMixin): bookmark2 = self.setup_bookmark() bookmark3 = self.setup_bookmark() - self.client.post(reverse('bookmarks:index.action'), { - 'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)], - }) + self.client.post( + reverse("bookmarks:index.action"), + { + "bookmark_id": [ + str(bookmark1.id), + str(bookmark2.id), + str(bookmark3.id), + ], + }, + ) self.assertBookmarksAreUnmodified([bookmark1, bookmark2, bookmark3]) @@ -572,14 +796,25 @@ class BookmarkActionViewTestCase(TestCase, BookmarkFactoryMixin): bookmark2 = self.setup_bookmark() bookmark3 = self.setup_bookmark() - url = reverse('bookmarks:index.action') + '?return_url=' + reverse('bookmarks:settings.index') - response = self.client.post(url, { - 'bulk_action': ['bulk_archive'], - 'bulk_execute': [''], - 'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)], - }) + url = ( + reverse("bookmarks:index.action") + + "?return_url=" + + reverse("bookmarks:settings.index") + ) + response = self.client.post( + url, + { + "bulk_action": ["bulk_archive"], + "bulk_execute": [""], + "bookmark_id": [ + str(bookmark1.id), + str(bookmark2.id), + str(bookmark3.id), + ], + }, + ) - self.assertRedirects(response, reverse('bookmarks:settings.index')) + self.assertRedirects(response, reverse("bookmarks:settings.index")) def test_should_not_redirect_to_external_url(self): bookmark1 = self.setup_bookmark() @@ -587,19 +822,27 @@ class BookmarkActionViewTestCase(TestCase, BookmarkFactoryMixin): bookmark3 = self.setup_bookmark() def post_with(return_url, follow=None): - url = reverse('bookmarks:index.action') + f'?return_url={return_url}' - return self.client.post(url, { - 'bulk_action': ['bulk_archive'], - 'bulk_execute': [''], - 'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)], - }, follow=follow) + url = reverse("bookmarks:index.action") + f"?return_url={return_url}" + return self.client.post( + url, + { + "bulk_action": ["bulk_archive"], + "bulk_execute": [""], + "bookmark_id": [ + str(bookmark1.id), + str(bookmark2.id), + str(bookmark3.id), + ], + }, + follow=follow, + ) - response = post_with('https://example.com') - self.assertRedirects(response, reverse('bookmarks:index')) - response = post_with('//example.com') - self.assertRedirects(response, reverse('bookmarks:index')) - response = post_with('://example.com') - self.assertRedirects(response, reverse('bookmarks:index')) + response = post_with("https://example.com") + self.assertRedirects(response, reverse("bookmarks:index")) + response = post_with("//example.com") + self.assertRedirects(response, reverse("bookmarks:index")) + response = post_with("://example.com") + self.assertRedirects(response, reverse("bookmarks:index")) - response = post_with('/foo//example.com', follow=True) + response = post_with("/foo//example.com", follow=True) self.assertEqual(response.status_code, 404) diff --git a/bookmarks/tests/test_bookmark_archived_view.py b/bookmarks/tests/test_bookmark_archived_view.py index 4702b7d..a082487 100644 --- a/bookmarks/tests/test_bookmark_archived_view.py +++ b/bookmarks/tests/test_bookmark_archived_view.py @@ -6,7 +6,11 @@ from django.test import TestCase from django.urls import reverse from bookmarks.models import Bookmark, BookmarkSearch, Tag, UserProfile -from bookmarks.tests.helpers import BookmarkFactoryMixin, HtmlTestMixin, collapse_whitespace +from bookmarks.tests.helpers import ( + BookmarkFactoryMixin, + HtmlTestMixin, + collapse_whitespace, +) class BookmarkArchivedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin): @@ -15,33 +19,41 @@ class BookmarkArchivedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin user = self.get_or_create_test_user() self.client.force_login(user) - def assertVisibleBookmarks(self, response, bookmarks: List[Bookmark], link_target: str = '_blank'): + def assertVisibleBookmarks( + self, response, bookmarks: List[Bookmark], link_target: str = "_blank" + ): soup = self.make_soup(response.content.decode()) - bookmark_list = soup.select_one(f'ul.bookmark-list[data-bookmarks-total="{len(bookmarks)}"]') + bookmark_list = soup.select_one( + f'ul.bookmark-list[data-bookmarks-total="{len(bookmarks)}"]' + ) self.assertIsNotNone(bookmark_list) - bookmark_items = bookmark_list.select('li[ld-bookmark-item]') + bookmark_items = bookmark_list.select("li[ld-bookmark-item]") self.assertEqual(len(bookmark_items), len(bookmarks)) for bookmark in bookmarks: bookmark_item = bookmark_list.select_one( - f'li[ld-bookmark-item] a[href="{bookmark.url}"][target="{link_target}"]') + f'li[ld-bookmark-item] a[href="{bookmark.url}"][target="{link_target}"]' + ) self.assertIsNotNone(bookmark_item) - def assertInvisibleBookmarks(self, response, bookmarks: List[Bookmark], link_target: str = '_blank'): + def assertInvisibleBookmarks( + self, response, bookmarks: List[Bookmark], link_target: str = "_blank" + ): soup = self.make_soup(response.content.decode()) for bookmark in bookmarks: bookmark_item = soup.select_one( - f'li[ld-bookmark-item] a[href="{bookmark.url}"][target="{link_target}"]') + f'li[ld-bookmark-item] a[href="{bookmark.url}"][target="{link_target}"]' + ) self.assertIsNone(bookmark_item) def assertVisibleTags(self, response, tags: List[Tag]): soup = self.make_soup(response.content.decode()) - tag_cloud = soup.select_one('div.tag-cloud') + tag_cloud = soup.select_one("div.tag-cloud") self.assertIsNotNone(tag_cloud) - tag_items = tag_cloud.select('a[data-is-tag-item]') + tag_items = tag_cloud.select("a[data-is-tag-item]") self.assertEqual(len(tag_items), len(tags)) tag_item_names = [tag_item.text.strip() for tag_item in tag_items] @@ -51,7 +63,7 @@ class BookmarkArchivedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin def assertInvisibleTags(self, response, tags: List[Tag]): soup = self.make_soup(response.content.decode()) - tag_items = soup.select('a[data-is-tag-item]') + tag_items = soup.select("a[data-is-tag-item]") tag_item_names = [tag_item.text.strip() for tag_item in tag_items] @@ -60,78 +72,103 @@ class BookmarkArchivedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin def assertSelectedTags(self, response, tags: List[Tag]): soup = self.make_soup(response.content.decode()) - selected_tags = soup.select_one('p.selected-tags') + selected_tags = soup.select_one("p.selected-tags") self.assertIsNotNone(selected_tags) - tag_list = selected_tags.select('a') + tag_list = selected_tags.select("a") self.assertEqual(len(tag_list), len(tags)) for tag in tags: - self.assertTrue(tag.name in selected_tags.text, msg=f'Selected tags do not contain: {tag.name}') + self.assertTrue( + tag.name in selected_tags.text, + msg=f"Selected tags do not contain: {tag.name}", + ) def assertEditLink(self, response, url): html = response.content.decode() - self.assertInHTML(f''' + self.assertInHTML( + f""" Edit - ''', html) + """, + html, + ) def assertBulkActionForm(self, response, url: str): html = collapse_whitespace(response.content.decode()) - needle = collapse_whitespace(f''' + needle = collapse_whitespace( + f"""

- ''') + """ + ) self.assertIn(needle, html) def test_should_list_archived_and_user_owned_bookmarks(self): - other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123') + other_user = User.objects.create_user( + "otheruser", "otheruser@example.com", "password123" + ) visible_bookmarks = self.setup_numbered_bookmarks(3, archived=True) invisible_bookmarks = [ self.setup_bookmark(is_archived=False), self.setup_bookmark(is_archived=True, user=other_user), ] - response = self.client.get(reverse('bookmarks:archived')) + response = self.client.get(reverse("bookmarks:archived")) self.assertVisibleBookmarks(response, visible_bookmarks) self.assertInvisibleBookmarks(response, invisible_bookmarks) def test_should_list_bookmarks_matching_query(self): - visible_bookmarks = self.setup_numbered_bookmarks(3, prefix='foo', archived=True) - invisible_bookmarks = self.setup_numbered_bookmarks(3, prefix='bar', archived=True) + visible_bookmarks = self.setup_numbered_bookmarks( + 3, prefix="foo", archived=True + ) + invisible_bookmarks = self.setup_numbered_bookmarks( + 3, prefix="bar", archived=True + ) - response = self.client.get(reverse('bookmarks:archived') + '?q=foo') + response = self.client.get(reverse("bookmarks:archived") + "?q=foo") html = collapse_whitespace(response.content.decode()) self.assertVisibleBookmarks(response, visible_bookmarks) self.assertInvisibleBookmarks(response, invisible_bookmarks) def test_should_list_tags_for_archived_and_user_owned_bookmarks(self): - other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123') - visible_bookmarks = self.setup_numbered_bookmarks(3, with_tags=True, archived=True) - unarchived_bookmarks = self.setup_numbered_bookmarks(3, with_tags=True, archived=False, tag_prefix='unarchived') - other_user_bookmarks = self.setup_numbered_bookmarks(3, with_tags=True, archived=True, user=other_user, - tag_prefix='otheruser') + other_user = User.objects.create_user( + "otheruser", "otheruser@example.com", "password123" + ) + visible_bookmarks = self.setup_numbered_bookmarks( + 3, with_tags=True, archived=True + ) + unarchived_bookmarks = self.setup_numbered_bookmarks( + 3, with_tags=True, archived=False, tag_prefix="unarchived" + ) + other_user_bookmarks = self.setup_numbered_bookmarks( + 3, with_tags=True, archived=True, user=other_user, tag_prefix="otheruser" + ) visible_tags = self.get_tags_from_bookmarks(visible_bookmarks) - invisible_tags = self.get_tags_from_bookmarks(unarchived_bookmarks + other_user_bookmarks) + invisible_tags = self.get_tags_from_bookmarks( + unarchived_bookmarks + other_user_bookmarks + ) - response = self.client.get(reverse('bookmarks:archived')) + response = self.client.get(reverse("bookmarks:archived")) self.assertVisibleTags(response, visible_tags) self.assertInvisibleTags(response, invisible_tags) def test_should_list_tags_for_bookmarks_matching_query(self): - visible_bookmarks = self.setup_numbered_bookmarks(3, with_tags=True, archived=True, prefix='foo', - tag_prefix='foo') - invisible_bookmarks = self.setup_numbered_bookmarks(3, with_tags=True, archived=True, prefix='bar', - tag_prefix='bar') + visible_bookmarks = self.setup_numbered_bookmarks( + 3, with_tags=True, archived=True, prefix="foo", tag_prefix="foo" + ) + invisible_bookmarks = self.setup_numbered_bookmarks( + 3, with_tags=True, archived=True, prefix="bar", tag_prefix="bar" + ) visible_tags = self.get_tags_from_bookmarks(visible_bookmarks) invisible_tags = self.get_tags_from_bookmarks(invisible_bookmarks) - response = self.client.get(reverse('bookmarks:archived') + '?q=foo') + response = self.client.get(reverse("bookmarks:archived") + "?q=foo") self.assertVisibleTags(response, visible_tags) self.assertInvisibleTags(response, invisible_tags) @@ -139,19 +176,31 @@ class BookmarkArchivedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin def test_should_list_bookmarks_and_tags_for_search_preferences(self): user_profile = self.user.profile user_profile.search_preferences = { - 'unread': BookmarkSearch.FILTER_UNREAD_YES, + "unread": BookmarkSearch.FILTER_UNREAD_YES, } user_profile.save() - unread_bookmarks = self.setup_numbered_bookmarks(3, archived=True, unread=True, with_tags=True, prefix='unread', - tag_prefix='unread') - read_bookmarks = self.setup_numbered_bookmarks(3, archived=True, unread=False, with_tags=True, prefix='read', - tag_prefix='read') + unread_bookmarks = self.setup_numbered_bookmarks( + 3, + archived=True, + unread=True, + with_tags=True, + prefix="unread", + tag_prefix="unread", + ) + read_bookmarks = self.setup_numbered_bookmarks( + 3, + archived=True, + unread=False, + with_tags=True, + prefix="read", + tag_prefix="read", + ) unread_tags = self.get_tags_from_bookmarks(unread_bookmarks) read_tags = self.get_tags_from_bookmarks(read_bookmarks) - response = self.client.get(reverse('bookmarks:archived')) + response = self.client.get(reverse("bookmarks:archived")) self.assertVisibleBookmarks(response, unread_bookmarks) self.assertInvisibleBookmarks(response, read_bookmarks) self.assertVisibleTags(response, unread_tags) @@ -167,11 +216,15 @@ class BookmarkArchivedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin ] self.setup_bookmark(is_archived=True, tags=tags) - response = self.client.get(reverse('bookmarks:archived') + f'?q=%23{tags[0].name}+%23{tags[1].name}') + response = self.client.get( + reverse("bookmarks:archived") + f"?q=%23{tags[0].name}+%23{tags[1].name}" + ) self.assertSelectedTags(response, [tags[0], tags[1]]) - def test_should_not_display_search_terms_from_query_as_selected_tags_in_strict_mode(self): + def test_should_not_display_search_terms_from_query_as_selected_tags_in_strict_mode( + self, + ): tags = [ self.setup_tag(), self.setup_tag(), @@ -181,7 +234,10 @@ class BookmarkArchivedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin ] self.setup_bookmark(title=tags[0].name, tags=tags, is_archived=True) - response = self.client.get(reverse('bookmarks:archived') + f'?q={tags[0].name}+%23{tags[1].name.upper()}') + response = self.client.get( + reverse("bookmarks:archived") + + f"?q={tags[0].name}+%23{tags[1].name.upper()}" + ) self.assertSelectedTags(response, [tags[1]]) @@ -198,16 +254,19 @@ class BookmarkArchivedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin ] self.setup_bookmark(tags=tags, is_archived=True) - response = self.client.get(reverse('bookmarks:archived') + f'?q={tags[0].name}+%23{tags[1].name.upper()}') + response = self.client.get( + reverse("bookmarks:archived") + + f"?q={tags[0].name}+%23{tags[1].name.upper()}" + ) self.assertSelectedTags(response, [tags[0], tags[1]]) def test_should_open_bookmarks_in_new_page_by_default(self): visible_bookmarks = self.setup_numbered_bookmarks(3, archived=True) - response = self.client.get(reverse('bookmarks:archived')) + response = self.client.get(reverse("bookmarks:archived")) - self.assertVisibleBookmarks(response, visible_bookmarks, '_blank') + self.assertVisibleBookmarks(response, visible_bookmarks, "_blank") def test_should_open_bookmarks_in_same_page_if_specified_in_user_profile(self): user = self.get_or_create_test_user() @@ -216,71 +275,72 @@ class BookmarkArchivedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin visible_bookmarks = self.setup_numbered_bookmarks(3, archived=True) - response = self.client.get(reverse('bookmarks:archived')) + response = self.client.get(reverse("bookmarks:archived")) - self.assertVisibleBookmarks(response, visible_bookmarks, '_self') + self.assertVisibleBookmarks(response, visible_bookmarks, "_self") def test_edit_link_return_url_respects_search_options(self): - bookmark = self.setup_bookmark(title='foo', is_archived=True) - edit_url = reverse('bookmarks:edit', args=[bookmark.id]) - base_url = reverse('bookmarks:archived') + bookmark = self.setup_bookmark(title="foo", is_archived=True) + edit_url = reverse("bookmarks:edit", args=[bookmark.id]) + base_url = reverse("bookmarks:archived") # without query params return_url = urllib.parse.quote(base_url) - url = f'{edit_url}?return_url={return_url}' + url = f"{edit_url}?return_url={return_url}" response = self.client.get(base_url) self.assertEditLink(response, url) # with query - url_params = '?q=foo' + url_params = "?q=foo" return_url = urllib.parse.quote(base_url + url_params) - url = f'{edit_url}?return_url={return_url}' + url = f"{edit_url}?return_url={return_url}" response = self.client.get(base_url + url_params) self.assertEditLink(response, url) # with query and sort and page - url_params = '?q=foo&sort=title_asc&page=2' + url_params = "?q=foo&sort=title_asc&page=2" return_url = urllib.parse.quote(base_url + url_params) - url = f'{edit_url}?return_url={return_url}' + url = f"{edit_url}?return_url={return_url}" response = self.client.get(base_url + url_params) self.assertEditLink(response, url) def test_bulk_edit_respects_search_options(self): - action_url = reverse('bookmarks:archived.action') - base_url = reverse('bookmarks:archived') + action_url = reverse("bookmarks:archived.action") + base_url = reverse("bookmarks:archived") # without params return_url = urllib.parse.quote_plus(base_url) - url = f'{action_url}?return_url={return_url}' + url = f"{action_url}?return_url={return_url}" response = self.client.get(base_url) self.assertBulkActionForm(response, url) # with query - url_params = '?q=foo' + url_params = "?q=foo" return_url = urllib.parse.quote_plus(base_url + url_params) - url = f'{action_url}?q=foo&return_url={return_url}' + url = f"{action_url}?q=foo&return_url={return_url}" response = self.client.get(base_url + url_params) self.assertBulkActionForm(response, url) # with query and sort - url_params = '?q=foo&sort=title_asc' + url_params = "?q=foo&sort=title_asc" return_url = urllib.parse.quote_plus(base_url + url_params) - url = f'{action_url}?q=foo&sort=title_asc&return_url={return_url}' + url = f"{action_url}?q=foo&sort=title_asc&return_url={return_url}" response = self.client.get(base_url + url_params) self.assertBulkActionForm(response, url) def test_allowed_bulk_actions(self): - url = reverse('bookmarks:archived') + url = reverse("bookmarks:archived") response = self.client.get(url) html = response.content.decode() - self.assertInHTML(f''' + self.assertInHTML( + f""" - ''', html) + """, + html, + ) def test_allowed_bulk_actions_with_sharing_enabled(self): user_profile = self.user.profile user_profile.enable_sharing = True user_profile.save() - url = reverse('bookmarks:archived') + url = reverse("bookmarks:archived") response = self.client.get(url) html = response.content.decode() - self.assertInHTML(f''' + self.assertInHTML( + f""" - ''', html) + """, + html, + ) def test_apply_search_preferences(self): # no params - response = self.client.post(reverse('bookmarks:archived')) + response = self.client.post(reverse("bookmarks:archived")) self.assertEqual(response.status_code, 302) - self.assertEqual(response.url, reverse('bookmarks:archived')) + self.assertEqual(response.url, reverse("bookmarks:archived")) # some params - response = self.client.post(reverse('bookmarks:archived'), { - 'q': 'foo', - 'sort': BookmarkSearch.SORT_TITLE_ASC, - }) + response = self.client.post( + reverse("bookmarks:archived"), + { + "q": "foo", + "sort": BookmarkSearch.SORT_TITLE_ASC, + }, + ) self.assertEqual(response.status_code, 302) - self.assertEqual(response.url, reverse('bookmarks:archived') + '?q=foo&sort=title_asc') + self.assertEqual( + response.url, reverse("bookmarks:archived") + "?q=foo&sort=title_asc" + ) # params with default value are removed - response = self.client.post(reverse('bookmarks:archived'), { - 'q': 'foo', - 'user': '', - 'sort': BookmarkSearch.SORT_ADDED_DESC, - 'shared': BookmarkSearch.FILTER_SHARED_OFF, - 'unread': BookmarkSearch.FILTER_UNREAD_YES, - }) + response = self.client.post( + reverse("bookmarks:archived"), + { + "q": "foo", + "user": "", + "sort": BookmarkSearch.SORT_ADDED_DESC, + "shared": BookmarkSearch.FILTER_SHARED_OFF, + "unread": BookmarkSearch.FILTER_UNREAD_YES, + }, + ) self.assertEqual(response.status_code, 302) - self.assertEqual(response.url, reverse('bookmarks:archived') + '?q=foo&unread=yes') + self.assertEqual( + response.url, reverse("bookmarks:archived") + "?q=foo&unread=yes" + ) # page is removed - response = self.client.post(reverse('bookmarks:archived'), { - 'q': 'foo', - 'page': '2', - 'sort': BookmarkSearch.SORT_TITLE_ASC, - }) + response = self.client.post( + reverse("bookmarks:archived"), + { + "q": "foo", + "page": "2", + "sort": BookmarkSearch.SORT_TITLE_ASC, + }, + ) self.assertEqual(response.status_code, 302) - self.assertEqual(response.url, reverse('bookmarks:archived') + '?q=foo&sort=title_asc') + self.assertEqual( + response.url, reverse("bookmarks:archived") + "?q=foo&sort=title_asc" + ) def test_save_search_preferences(self): user_profile = self.user.profile # no params - self.client.post(reverse('bookmarks:archived'), { - 'save': '', - }) + self.client.post( + reverse("bookmarks:archived"), + { + "save": "", + }, + ) user_profile.refresh_from_db() - self.assertEqual(user_profile.search_preferences, { - 'sort': BookmarkSearch.SORT_ADDED_DESC, - 'shared': BookmarkSearch.FILTER_SHARED_OFF, - 'unread': BookmarkSearch.FILTER_UNREAD_OFF, - }) + self.assertEqual( + user_profile.search_preferences, + { + "sort": BookmarkSearch.SORT_ADDED_DESC, + "shared": BookmarkSearch.FILTER_SHARED_OFF, + "unread": BookmarkSearch.FILTER_UNREAD_OFF, + }, + ) # with param - self.client.post(reverse('bookmarks:archived'), { - 'save': '', - 'sort': BookmarkSearch.SORT_TITLE_ASC, - }) + self.client.post( + reverse("bookmarks:archived"), + { + "save": "", + "sort": BookmarkSearch.SORT_TITLE_ASC, + }, + ) user_profile.refresh_from_db() - self.assertEqual(user_profile.search_preferences, { - 'sort': BookmarkSearch.SORT_TITLE_ASC, - 'shared': BookmarkSearch.FILTER_SHARED_OFF, - 'unread': BookmarkSearch.FILTER_UNREAD_OFF, - }) + self.assertEqual( + user_profile.search_preferences, + { + "sort": BookmarkSearch.SORT_TITLE_ASC, + "shared": BookmarkSearch.FILTER_SHARED_OFF, + "unread": BookmarkSearch.FILTER_UNREAD_OFF, + }, + ) # add a param - self.client.post(reverse('bookmarks:archived'), { - 'save': '', - 'sort': BookmarkSearch.SORT_TITLE_ASC, - 'unread': BookmarkSearch.FILTER_UNREAD_YES, - }) + self.client.post( + reverse("bookmarks:archived"), + { + "save": "", + "sort": BookmarkSearch.SORT_TITLE_ASC, + "unread": BookmarkSearch.FILTER_UNREAD_YES, + }, + ) user_profile.refresh_from_db() - self.assertEqual(user_profile.search_preferences, { - 'sort': BookmarkSearch.SORT_TITLE_ASC, - 'shared': BookmarkSearch.FILTER_SHARED_OFF, - 'unread': BookmarkSearch.FILTER_UNREAD_YES, - }) + self.assertEqual( + user_profile.search_preferences, + { + "sort": BookmarkSearch.SORT_TITLE_ASC, + "shared": BookmarkSearch.FILTER_SHARED_OFF, + "unread": BookmarkSearch.FILTER_UNREAD_YES, + }, + ) # remove a param - self.client.post(reverse('bookmarks:archived'), { - 'save': '', - 'unread': BookmarkSearch.FILTER_UNREAD_YES, - }) + self.client.post( + reverse("bookmarks:archived"), + { + "save": "", + "unread": BookmarkSearch.FILTER_UNREAD_YES, + }, + ) user_profile.refresh_from_db() - self.assertEqual(user_profile.search_preferences, { - 'sort': BookmarkSearch.SORT_ADDED_DESC, - 'shared': BookmarkSearch.FILTER_SHARED_OFF, - 'unread': BookmarkSearch.FILTER_UNREAD_YES, - }) + self.assertEqual( + user_profile.search_preferences, + { + "sort": BookmarkSearch.SORT_ADDED_DESC, + "shared": BookmarkSearch.FILTER_SHARED_OFF, + "unread": BookmarkSearch.FILTER_UNREAD_YES, + }, + ) # ignores non-preferences - self.client.post(reverse('bookmarks:archived'), { - 'save': '', - 'q': 'foo', - 'user': 'john', - 'page': '3', - 'sort': BookmarkSearch.SORT_TITLE_ASC, - }) + self.client.post( + reverse("bookmarks:archived"), + { + "save": "", + "q": "foo", + "user": "john", + "page": "3", + "sort": BookmarkSearch.SORT_TITLE_ASC, + }, + ) user_profile.refresh_from_db() - self.assertEqual(user_profile.search_preferences, { - 'sort': BookmarkSearch.SORT_TITLE_ASC, - 'shared': BookmarkSearch.FILTER_SHARED_OFF, - 'unread': BookmarkSearch.FILTER_UNREAD_OFF, - }) + self.assertEqual( + user_profile.search_preferences, + { + "sort": BookmarkSearch.SORT_TITLE_ASC, + "shared": BookmarkSearch.FILTER_SHARED_OFF, + "unread": BookmarkSearch.FILTER_UNREAD_OFF, + }, + ) def test_url_encode_bookmark_actions_url(self): - url = reverse('bookmarks:archived') + '?q=%23foo' + url = reverse("bookmarks:archived") + "?q=%23foo" response = self.client.get(url) html = response.content.decode() soup = self.make_soup(html) - actions_form = soup.select('form.bookmark-actions')[0] + actions_form = soup.select("form.bookmark-actions")[0] - self.assertEqual(actions_form.attrs['action'], - '/bookmarks/archived/action?q=%23foo&return_url=%2Fbookmarks%2Farchived%3Fq%3D%2523foo') + self.assertEqual( + actions_form.attrs["action"], + "/bookmarks/archived/action?q=%23foo&return_url=%2Fbookmarks%2Farchived%3Fq%3D%2523foo", + ) def test_encode_search_params(self): - bookmark = self.setup_bookmark(description='alert(\'xss\')', is_archived=True) + bookmark = self.setup_bookmark(description="alert('xss')", is_archived=True) - url = reverse('bookmarks:archived') + '?q=alert(%27xss%27)' + url = reverse("bookmarks:archived") + "?q=alert(%27xss%27)" response = self.client.get(url) - self.assertNotContains(response, 'alert(\'xss\')') + self.assertNotContains(response, "alert('xss')") self.assertContains(response, bookmark.url) - url = reverse('bookmarks:archived') + '?sort=alert(%27xss%27)' + url = reverse("bookmarks:archived") + "?sort=alert(%27xss%27)" response = self.client.get(url) - self.assertNotContains(response, 'alert(\'xss\')') + self.assertNotContains(response, "alert('xss')") - url = reverse('bookmarks:archived') + '?unread=alert(%27xss%27)' + url = reverse("bookmarks:archived") + "?unread=alert(%27xss%27)" response = self.client.get(url) - self.assertNotContains(response, 'alert(\'xss\')') + self.assertNotContains(response, "alert('xss')") - url = reverse('bookmarks:archived') + '?shared=alert(%27xss%27)' + url = reverse("bookmarks:archived") + "?shared=alert(%27xss%27)" response = self.client.get(url) - self.assertNotContains(response, 'alert(\'xss\')') + self.assertNotContains(response, "alert('xss')") - url = reverse('bookmarks:archived') + '?user=alert(%27xss%27)' + url = reverse("bookmarks:archived") + "?user=alert(%27xss%27)" response = self.client.get(url) - self.assertNotContains(response, 'alert(\'xss\')') + self.assertNotContains(response, "alert('xss')") - url = reverse('bookmarks:archived') + '?page=alert(%27xss%27)' + url = reverse("bookmarks:archived") + "?page=alert(%27xss%27)" response = self.client.get(url) - self.assertNotContains(response, 'alert(\'xss\')') + self.assertNotContains(response, "alert('xss')") diff --git a/bookmarks/tests/test_bookmark_archived_view_performance.py b/bookmarks/tests/test_bookmark_archived_view_performance.py index 3336e9f..81042b6 100644 --- a/bookmarks/tests/test_bookmark_archived_view_performance.py +++ b/bookmarks/tests/test_bookmark_archived_view_performance.py @@ -8,7 +8,9 @@ from django.db.utils import DEFAULT_DB_ALIAS from bookmarks.tests.helpers import BookmarkFactoryMixin -class BookmarkArchivedViewPerformanceTestCase(TransactionTestCase, BookmarkFactoryMixin): +class BookmarkArchivedViewPerformanceTestCase( + TransactionTestCase, BookmarkFactoryMixin +): def setUp(self) -> None: user = self.get_or_create_test_user() @@ -26,8 +28,10 @@ class BookmarkArchivedViewPerformanceTestCase(TransactionTestCase, BookmarkFacto # capture number of queries context = CaptureQueriesContext(self.get_connection()) with context: - response = self.client.get(reverse('bookmarks:archived')) - self.assertContains(response, '
  • ', num_initial_bookmarks) + response = self.client.get(reverse("bookmarks:archived")) + self.assertContains( + response, "
  • ", num_initial_bookmarks + ) number_of_queries = context.final_queries @@ -38,5 +42,9 @@ class BookmarkArchivedViewPerformanceTestCase(TransactionTestCase, BookmarkFacto # assert num queries doesn't increase with self.assertNumQueries(number_of_queries): - response = self.client.get(reverse('bookmarks:archived')) - self.assertContains(response, '
  • ', num_initial_bookmarks + num_additional_bookmarks) + response = self.client.get(reverse("bookmarks:archived")) + self.assertContains( + response, + "
  • ", + num_initial_bookmarks + num_additional_bookmarks, + ) diff --git a/bookmarks/tests/test_bookmark_edit_view.py b/bookmarks/tests/test_bookmark_edit_view.py index 99b86e7..ee5de9f 100644 --- a/bookmarks/tests/test_bookmark_edit_view.py +++ b/bookmarks/tests/test_bookmark_edit_view.py @@ -16,153 +16,192 @@ class BookmarkEditViewTestCase(TestCase, BookmarkFactoryMixin): if overrides is None: overrides = {} form_data = { - 'url': 'http://example.com/edited', - 'tag_string': 'editedtag1 editedtag2', - 'title': 'edited title', - 'description': 'edited description', - 'notes': 'edited notes', - 'unread': False, - 'shared': False, + "url": "http://example.com/edited", + "tag_string": "editedtag1 editedtag2", + "title": "edited title", + "description": "edited description", + "notes": "edited notes", + "unread": False, + "shared": False, } return {**form_data, **overrides} def test_should_edit_bookmark(self): bookmark = self.setup_bookmark() - form_data = self.create_form_data({'id': bookmark.id}) + form_data = self.create_form_data({"id": bookmark.id}) - self.client.post(reverse('bookmarks:edit', args=[bookmark.id]), form_data) + self.client.post(reverse("bookmarks:edit", args=[bookmark.id]), form_data) bookmark.refresh_from_db() self.assertEqual(bookmark.owner, self.user) - self.assertEqual(bookmark.url, form_data['url']) - self.assertEqual(bookmark.title, form_data['title']) - self.assertEqual(bookmark.description, form_data['description']) - self.assertEqual(bookmark.notes, form_data['notes']) - self.assertEqual(bookmark.unread, form_data['unread']) - self.assertEqual(bookmark.shared, form_data['shared']) + self.assertEqual(bookmark.url, form_data["url"]) + self.assertEqual(bookmark.title, form_data["title"]) + self.assertEqual(bookmark.description, form_data["description"]) + self.assertEqual(bookmark.notes, form_data["notes"]) + self.assertEqual(bookmark.unread, form_data["unread"]) + self.assertEqual(bookmark.shared, form_data["shared"]) self.assertEqual(bookmark.tags.count(), 2) - tags = bookmark.tags.order_by('name').all() - self.assertEqual(tags[0].name, 'editedtag1') - self.assertEqual(tags[1].name, 'editedtag2') + tags = bookmark.tags.order_by("name").all() + self.assertEqual(tags[0].name, "editedtag1") + self.assertEqual(tags[1].name, "editedtag2") def test_should_edit_unread_state(self): bookmark = self.setup_bookmark() - form_data = self.create_form_data({'id': bookmark.id, 'unread': True}) - self.client.post(reverse('bookmarks:edit', args=[bookmark.id]), form_data) + form_data = self.create_form_data({"id": bookmark.id, "unread": True}) + self.client.post(reverse("bookmarks:edit", args=[bookmark.id]), form_data) bookmark.refresh_from_db() self.assertTrue(bookmark.unread) - form_data = self.create_form_data({'id': bookmark.id, 'unread': False}) - self.client.post(reverse('bookmarks:edit', args=[bookmark.id]), form_data) + form_data = self.create_form_data({"id": bookmark.id, "unread": False}) + self.client.post(reverse("bookmarks:edit", args=[bookmark.id]), form_data) bookmark.refresh_from_db() self.assertFalse(bookmark.unread) def test_should_edit_shared_state(self): bookmark = self.setup_bookmark() - form_data = self.create_form_data({'id': bookmark.id, 'shared': True}) - self.client.post(reverse('bookmarks:edit', args=[bookmark.id]), form_data) + form_data = self.create_form_data({"id": bookmark.id, "shared": True}) + self.client.post(reverse("bookmarks:edit", args=[bookmark.id]), form_data) bookmark.refresh_from_db() self.assertTrue(bookmark.shared) - form_data = self.create_form_data({'id': bookmark.id, 'shared': False}) - self.client.post(reverse('bookmarks:edit', args=[bookmark.id]), form_data) + form_data = self.create_form_data({"id": bookmark.id, "shared": False}) + self.client.post(reverse("bookmarks:edit", args=[bookmark.id]), form_data) bookmark.refresh_from_db() self.assertFalse(bookmark.shared) def test_should_prefill_bookmark_form_fields(self): tag1 = self.setup_tag() tag2 = self.setup_tag() - bookmark = self.setup_bookmark(tags=[tag1, tag2], title='edited title', description='edited description', - notes='edited notes', website_title='website title', - website_description='website description') + bookmark = self.setup_bookmark( + tags=[tag1, tag2], + title="edited title", + description="edited description", + notes="edited notes", + website_title="website title", + website_description="website description", + ) - response = self.client.get(reverse('bookmarks:edit', args=[bookmark.id])) + response = self.client.get(reverse("bookmarks:edit", args=[bookmark.id])) html = response.content.decode() - self.assertInHTML(f''' + self.assertInHTML( + f""" - ''', html) + """, + html, + ) - tag_string = build_tag_string(bookmark.tag_names, ' ') - self.assertInHTML(f''' + tag_string = build_tag_string(bookmark.tag_names, " ") + self.assertInHTML( + f""" - ''', html) + """, + html, + ) - self.assertInHTML(f''' + self.assertInHTML( + f""" - ''', html) + """, + html, + ) - self.assertInHTML(f''' + self.assertInHTML( + f""" - ''', html) + """, + html, + ) - self.assertInHTML(f''' + self.assertInHTML( + f""" - ''', html) + """, + html, + ) - self.assertInHTML(f''' + self.assertInHTML( + f""" - ''', html) + """, + html, + ) - self.assertInHTML(f''' + self.assertInHTML( + f""" - ''', html) + """, + html, + ) def test_should_redirect_to_return_url(self): bookmark = self.setup_bookmark() form_data = self.create_form_data() - url = reverse('bookmarks:edit', args=[bookmark.id]) + '?return_url=' + reverse('bookmarks:close') + url = ( + reverse("bookmarks:edit", args=[bookmark.id]) + + "?return_url=" + + reverse("bookmarks:close") + ) response = self.client.post(url, form_data) - self.assertRedirects(response, reverse('bookmarks:close')) + self.assertRedirects(response, reverse("bookmarks:close")) def test_should_redirect_to_bookmark_index_by_default(self): bookmark = self.setup_bookmark() form_data = self.create_form_data() - response = self.client.post(reverse('bookmarks:edit', args=[bookmark.id]), form_data) + response = self.client.post( + reverse("bookmarks:edit", args=[bookmark.id]), form_data + ) - self.assertRedirects(response, reverse('bookmarks:index')) + self.assertRedirects(response, reverse("bookmarks:index")) def test_should_not_redirect_to_external_url(self): bookmark = self.setup_bookmark() def post_with(return_url, follow=None): form_data = self.create_form_data() - url = reverse('bookmarks:edit', args=[bookmark.id]) + f'?return_url={return_url}' + url = ( + reverse("bookmarks:edit", args=[bookmark.id]) + + f"?return_url={return_url}" + ) return self.client.post(url, form_data, follow=follow) - response = post_with('https://example.com') - self.assertRedirects(response, reverse('bookmarks:index')) - response = post_with('//example.com') - self.assertRedirects(response, reverse('bookmarks:index')) - response = post_with('://example.com') - self.assertRedirects(response, reverse('bookmarks:index')) + response = post_with("https://example.com") + self.assertRedirects(response, reverse("bookmarks:index")) + response = post_with("//example.com") + self.assertRedirects(response, reverse("bookmarks:index")) + response = post_with("://example.com") + self.assertRedirects(response, reverse("bookmarks:index")) - response = post_with('/foo//example.com', follow=True) + response = post_with("/foo//example.com", follow=True) self.assertEqual(response.status_code, 404) def test_can_only_edit_own_bookmarks(self): - other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123') + other_user = User.objects.create_user( + "otheruser", "otheruser@example.com", "password123" + ) bookmark = self.setup_bookmark(user=other_user) - form_data = self.create_form_data({'id': bookmark.id}) + form_data = self.create_form_data({"id": bookmark.id}) - response = self.client.post(reverse('bookmarks:edit', args=[bookmark.id]), form_data) + response = self.client.post( + reverse("bookmarks:edit", args=[bookmark.id]), form_data + ) bookmark.refresh_from_db() - self.assertNotEqual(bookmark.url, form_data['url']) + self.assertNotEqual(bookmark.url, form_data["url"]) self.assertEqual(response.status_code, 404) def test_should_respect_share_profile_setting(self): @@ -170,38 +209,46 @@ class BookmarkEditViewTestCase(TestCase, BookmarkFactoryMixin): self.user.profile.enable_sharing = False self.user.profile.save() - response = self.client.get(reverse('bookmarks:edit', args=[bookmark.id])) + response = self.client.get(reverse("bookmarks:edit", args=[bookmark.id])) html = response.content.decode() - self.assertInHTML(''' + self.assertInHTML( + """ - ''', html, count=0) + """, + html, + count=0, + ) self.user.profile.enable_sharing = True self.user.profile.save() - response = self.client.get(reverse('bookmarks:edit', args=[bookmark.id])) + response = self.client.get(reverse("bookmarks:edit", args=[bookmark.id])) html = response.content.decode() - self.assertInHTML(''' + self.assertInHTML( + """ - ''', html, count=1) + """, + html, + count=1, + ) def test_should_hide_notes_if_there_are_no_notes(self): bookmark = self.setup_bookmark() - response = self.client.get(reverse('bookmarks:edit', args=[bookmark.id])) + response = self.client.get(reverse("bookmarks:edit", args=[bookmark.id])) self.assertContains(response, '
    ', count=1) def test_should_show_notes_if_there_are_notes(self): - bookmark = self.setup_bookmark(notes='test notes') - response = self.client.get(reverse('bookmarks:edit', args=[bookmark.id])) + bookmark = self.setup_bookmark(notes="test notes") + response = self.client.get(reverse("bookmarks:edit", args=[bookmark.id])) self.assertContains(response, '
    ', count=1) diff --git a/bookmarks/tests/test_bookmark_index_view.py b/bookmarks/tests/test_bookmark_index_view.py index 290dd37..9f1859f 100644 --- a/bookmarks/tests/test_bookmark_index_view.py +++ b/bookmarks/tests/test_bookmark_index_view.py @@ -6,7 +6,11 @@ from django.test import TestCase from django.urls import reverse from bookmarks.models import Bookmark, BookmarkSearch, Tag, UserProfile -from bookmarks.tests.helpers import BookmarkFactoryMixin, HtmlTestMixin, collapse_whitespace +from bookmarks.tests.helpers import ( + BookmarkFactoryMixin, + HtmlTestMixin, + collapse_whitespace, +) class BookmarkIndexViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin): @@ -15,33 +19,41 @@ class BookmarkIndexViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin): user = self.get_or_create_test_user() self.client.force_login(user) - def assertVisibleBookmarks(self, response, bookmarks: List[Bookmark], link_target: str = '_blank'): + def assertVisibleBookmarks( + self, response, bookmarks: List[Bookmark], link_target: str = "_blank" + ): soup = self.make_soup(response.content.decode()) - bookmark_list = soup.select_one(f'ul.bookmark-list[data-bookmarks-total="{len(bookmarks)}"]') + bookmark_list = soup.select_one( + f'ul.bookmark-list[data-bookmarks-total="{len(bookmarks)}"]' + ) self.assertIsNotNone(bookmark_list) - bookmark_items = bookmark_list.select('li[ld-bookmark-item]') + bookmark_items = bookmark_list.select("li[ld-bookmark-item]") self.assertEqual(len(bookmark_items), len(bookmarks)) for bookmark in bookmarks: bookmark_item = bookmark_list.select_one( - f'li[ld-bookmark-item] a[href="{bookmark.url}"][target="{link_target}"]') + f'li[ld-bookmark-item] a[href="{bookmark.url}"][target="{link_target}"]' + ) self.assertIsNotNone(bookmark_item) - def assertInvisibleBookmarks(self, response, bookmarks: List[Bookmark], link_target: str = '_blank'): + def assertInvisibleBookmarks( + self, response, bookmarks: List[Bookmark], link_target: str = "_blank" + ): soup = self.make_soup(response.content.decode()) for bookmark in bookmarks: bookmark_item = soup.select_one( - f'li[ld-bookmark-item] a[href="{bookmark.url}"][target="{link_target}"]') + f'li[ld-bookmark-item] a[href="{bookmark.url}"][target="{link_target}"]' + ) self.assertIsNone(bookmark_item) def assertVisibleTags(self, response, tags: List[Tag]): soup = self.make_soup(response.content.decode()) - tag_cloud = soup.select_one('div.tag-cloud') + tag_cloud = soup.select_one("div.tag-cloud") self.assertIsNotNone(tag_cloud) - tag_items = tag_cloud.select('a[data-is-tag-item]') + tag_items = tag_cloud.select("a[data-is-tag-item]") self.assertEqual(len(tag_items), len(tags)) tag_item_names = [tag_item.text.strip() for tag_item in tag_items] @@ -51,7 +63,7 @@ class BookmarkIndexViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin): def assertInvisibleTags(self, response, tags: List[Tag]): soup = self.make_soup(response.content.decode()) - tag_items = soup.select('a[data-is-tag-item]') + tag_items = soup.select("a[data-is-tag-item]") tag_item_names = [tag_item.text.strip() for tag_item in tag_items] @@ -60,74 +72,96 @@ class BookmarkIndexViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin): def assertSelectedTags(self, response, tags: List[Tag]): soup = self.make_soup(response.content.decode()) - selected_tags = soup.select_one('p.selected-tags') + selected_tags = soup.select_one("p.selected-tags") self.assertIsNotNone(selected_tags) - tag_list = selected_tags.select('a') + tag_list = selected_tags.select("a") self.assertEqual(len(tag_list), len(tags)) for tag in tags: - self.assertTrue(tag.name in selected_tags.text, msg=f'Selected tags do not contain: {tag.name}') + self.assertTrue( + tag.name in selected_tags.text, + msg=f"Selected tags do not contain: {tag.name}", + ) def assertEditLink(self, response, url): html = response.content.decode() - self.assertInHTML(f''' + self.assertInHTML( + f""" Edit - ''', html) + """, + html, + ) def assertBulkActionForm(self, response, url: str): html = collapse_whitespace(response.content.decode()) - needle = collapse_whitespace(f''' + needle = collapse_whitespace( + f""" - ''') + """ + ) self.assertIn(needle, html) def test_should_list_unarchived_and_user_owned_bookmarks(self): - other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123') + other_user = User.objects.create_user( + "otheruser", "otheruser@example.com", "password123" + ) visible_bookmarks = self.setup_numbered_bookmarks(3) invisible_bookmarks = [ self.setup_bookmark(is_archived=True), self.setup_bookmark(user=other_user), ] - response = self.client.get(reverse('bookmarks:index')) + response = self.client.get(reverse("bookmarks:index")) self.assertVisibleBookmarks(response, visible_bookmarks) self.assertInvisibleBookmarks(response, invisible_bookmarks) def test_should_list_bookmarks_matching_query(self): - visible_bookmarks = self.setup_numbered_bookmarks(3, prefix='foo') - invisible_bookmarks = self.setup_numbered_bookmarks(3, prefix='bar') + visible_bookmarks = self.setup_numbered_bookmarks(3, prefix="foo") + invisible_bookmarks = self.setup_numbered_bookmarks(3, prefix="bar") - response = self.client.get(reverse('bookmarks:index') + '?q=foo') + response = self.client.get(reverse("bookmarks:index") + "?q=foo") self.assertVisibleBookmarks(response, visible_bookmarks) self.assertInvisibleBookmarks(response, invisible_bookmarks) def test_should_list_tags_for_unarchived_and_user_owned_bookmarks(self): - other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123') + other_user = User.objects.create_user( + "otheruser", "otheruser@example.com", "password123" + ) visible_bookmarks = self.setup_numbered_bookmarks(3, with_tags=True) - archived_bookmarks = self.setup_numbered_bookmarks(3, with_tags=True, archived=True, tag_prefix='archived') - other_user_bookmarks = self.setup_numbered_bookmarks(3, with_tags=True, user=other_user, tag_prefix='otheruser') + archived_bookmarks = self.setup_numbered_bookmarks( + 3, with_tags=True, archived=True, tag_prefix="archived" + ) + other_user_bookmarks = self.setup_numbered_bookmarks( + 3, with_tags=True, user=other_user, tag_prefix="otheruser" + ) visible_tags = self.get_tags_from_bookmarks(visible_bookmarks) - invisible_tags = self.get_tags_from_bookmarks(archived_bookmarks + other_user_bookmarks) + invisible_tags = self.get_tags_from_bookmarks( + archived_bookmarks + other_user_bookmarks + ) - response = self.client.get(reverse('bookmarks:index')) + response = self.client.get(reverse("bookmarks:index")) self.assertVisibleTags(response, visible_tags) self.assertInvisibleTags(response, invisible_tags) def test_should_list_tags_for_bookmarks_matching_query(self): - visible_bookmarks = self.setup_numbered_bookmarks(3, with_tags=True, prefix='foo', tag_prefix='foo') - invisible_bookmarks = self.setup_numbered_bookmarks(3, with_tags=True, prefix='bar', tag_prefix='bar') + visible_bookmarks = self.setup_numbered_bookmarks( + 3, with_tags=True, prefix="foo", tag_prefix="foo" + ) + invisible_bookmarks = self.setup_numbered_bookmarks( + 3, with_tags=True, prefix="bar", tag_prefix="bar" + ) visible_tags = self.get_tags_from_bookmarks(visible_bookmarks) invisible_tags = self.get_tags_from_bookmarks(invisible_bookmarks) - response = self.client.get(reverse('bookmarks:index') + '?q=foo') + response = self.client.get(reverse("bookmarks:index") + "?q=foo") self.assertVisibleTags(response, visible_tags) self.assertInvisibleTags(response, invisible_tags) @@ -135,19 +169,21 @@ class BookmarkIndexViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin): def test_should_list_bookmarks_and_tags_for_search_preferences(self): user_profile = self.user.profile user_profile.search_preferences = { - 'unread': BookmarkSearch.FILTER_UNREAD_YES, + "unread": BookmarkSearch.FILTER_UNREAD_YES, } user_profile.save() - unread_bookmarks = self.setup_numbered_bookmarks(3, unread=True, with_tags=True, prefix='unread', - tag_prefix='unread') - read_bookmarks = self.setup_numbered_bookmarks(3, unread=False, with_tags=True, prefix='read', - tag_prefix='read') + unread_bookmarks = self.setup_numbered_bookmarks( + 3, unread=True, with_tags=True, prefix="unread", tag_prefix="unread" + ) + read_bookmarks = self.setup_numbered_bookmarks( + 3, unread=False, with_tags=True, prefix="read", tag_prefix="read" + ) unread_tags = self.get_tags_from_bookmarks(unread_bookmarks) read_tags = self.get_tags_from_bookmarks(read_bookmarks) - response = self.client.get(reverse('bookmarks:index')) + response = self.client.get(reverse("bookmarks:index")) self.assertVisibleBookmarks(response, unread_bookmarks) self.assertInvisibleBookmarks(response, read_bookmarks) self.assertVisibleTags(response, unread_tags) @@ -163,11 +199,16 @@ class BookmarkIndexViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin): ] self.setup_bookmark(tags=tags) - response = self.client.get(reverse('bookmarks:index') + f'?q=%23{tags[0].name}+%23{tags[1].name.upper()}') + response = self.client.get( + reverse("bookmarks:index") + + f"?q=%23{tags[0].name}+%23{tags[1].name.upper()}" + ) self.assertSelectedTags(response, [tags[0], tags[1]]) - def test_should_not_display_search_terms_from_query_as_selected_tags_in_strict_mode(self): + def test_should_not_display_search_terms_from_query_as_selected_tags_in_strict_mode( + self, + ): tags = [ self.setup_tag(), self.setup_tag(), @@ -177,7 +218,9 @@ class BookmarkIndexViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin): ] self.setup_bookmark(title=tags[0].name, tags=tags) - response = self.client.get(reverse('bookmarks:index') + f'?q={tags[0].name}+%23{tags[1].name.upper()}') + response = self.client.get( + reverse("bookmarks:index") + f"?q={tags[0].name}+%23{tags[1].name.upper()}" + ) self.assertSelectedTags(response, [tags[1]]) @@ -194,16 +237,18 @@ class BookmarkIndexViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin): ] self.setup_bookmark(tags=tags) - response = self.client.get(reverse('bookmarks:index') + f'?q={tags[0].name}+%23{tags[1].name.upper()}') + response = self.client.get( + reverse("bookmarks:index") + f"?q={tags[0].name}+%23{tags[1].name.upper()}" + ) self.assertSelectedTags(response, [tags[0], tags[1]]) def test_should_open_bookmarks_in_new_page_by_default(self): visible_bookmarks = self.setup_numbered_bookmarks(3) - response = self.client.get(reverse('bookmarks:index')) + response = self.client.get(reverse("bookmarks:index")) - self.assertVisibleBookmarks(response, visible_bookmarks, '_blank') + self.assertVisibleBookmarks(response, visible_bookmarks, "_blank") def test_should_open_bookmarks_in_same_page_if_specified_in_user_profile(self): user = self.get_or_create_test_user() @@ -212,71 +257,72 @@ class BookmarkIndexViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin): visible_bookmarks = self.setup_numbered_bookmarks(3) - response = self.client.get(reverse('bookmarks:index')) + response = self.client.get(reverse("bookmarks:index")) - self.assertVisibleBookmarks(response, visible_bookmarks, '_self') + self.assertVisibleBookmarks(response, visible_bookmarks, "_self") def test_edit_link_return_url_respects_search_options(self): - bookmark = self.setup_bookmark(title='foo') - edit_url = reverse('bookmarks:edit', args=[bookmark.id]) - base_url = reverse('bookmarks:index') + bookmark = self.setup_bookmark(title="foo") + edit_url = reverse("bookmarks:edit", args=[bookmark.id]) + base_url = reverse("bookmarks:index") # without query params return_url = urllib.parse.quote(base_url) - url = f'{edit_url}?return_url={return_url}' + url = f"{edit_url}?return_url={return_url}" response = self.client.get(base_url) self.assertEditLink(response, url) # with query - url_params = '?q=foo' + url_params = "?q=foo" return_url = urllib.parse.quote(base_url + url_params) - url = f'{edit_url}?return_url={return_url}' + url = f"{edit_url}?return_url={return_url}" response = self.client.get(base_url + url_params) self.assertEditLink(response, url) # with query and sort and page - url_params = '?q=foo&sort=title_asc&page=2' + url_params = "?q=foo&sort=title_asc&page=2" return_url = urllib.parse.quote(base_url + url_params) - url = f'{edit_url}?return_url={return_url}' + url = f"{edit_url}?return_url={return_url}" response = self.client.get(base_url + url_params) self.assertEditLink(response, url) def test_bulk_edit_respects_search_options(self): - action_url = reverse('bookmarks:index.action') - base_url = reverse('bookmarks:index') + action_url = reverse("bookmarks:index.action") + base_url = reverse("bookmarks:index") # without params return_url = urllib.parse.quote_plus(base_url) - url = f'{action_url}?return_url={return_url}' + url = f"{action_url}?return_url={return_url}" response = self.client.get(base_url) self.assertBulkActionForm(response, url) # with query - url_params = '?q=foo' + url_params = "?q=foo" return_url = urllib.parse.quote_plus(base_url + url_params) - url = f'{action_url}?q=foo&return_url={return_url}' + url = f"{action_url}?q=foo&return_url={return_url}" response = self.client.get(base_url + url_params) self.assertBulkActionForm(response, url) # with query and sort - url_params = '?q=foo&sort=title_asc' + url_params = "?q=foo&sort=title_asc" return_url = urllib.parse.quote_plus(base_url + url_params) - url = f'{action_url}?q=foo&sort=title_asc&return_url={return_url}' + url = f"{action_url}?q=foo&sort=title_asc&return_url={return_url}" response = self.client.get(base_url + url_params) self.assertBulkActionForm(response, url) def test_allowed_bulk_actions(self): - url = reverse('bookmarks:index') + url = reverse("bookmarks:index") response = self.client.get(url) html = response.content.decode() - self.assertInHTML(f''' + self.assertInHTML( + f""" - ''', html) + """, + html, + ) def test_allowed_bulk_actions_with_sharing_enabled(self): user_profile = self.user.profile user_profile.enable_sharing = True user_profile.save() - url = reverse('bookmarks:index') + url = reverse("bookmarks:index") response = self.client.get(url) html = response.content.decode() - self.assertInHTML(f''' + self.assertInHTML( + f""" - ''', html) + """, + html, + ) def test_apply_search_preferences(self): # no params - response = self.client.post(reverse('bookmarks:index')) + response = self.client.post(reverse("bookmarks:index")) self.assertEqual(response.status_code, 302) - self.assertEqual(response.url, reverse('bookmarks:index')) + self.assertEqual(response.url, reverse("bookmarks:index")) # some params - response = self.client.post(reverse('bookmarks:index'), { - 'q': 'foo', - 'sort': BookmarkSearch.SORT_TITLE_ASC, - }) + response = self.client.post( + reverse("bookmarks:index"), + { + "q": "foo", + "sort": BookmarkSearch.SORT_TITLE_ASC, + }, + ) self.assertEqual(response.status_code, 302) - self.assertEqual(response.url, reverse('bookmarks:index') + '?q=foo&sort=title_asc') + self.assertEqual( + response.url, reverse("bookmarks:index") + "?q=foo&sort=title_asc" + ) # params with default value are removed - response = self.client.post(reverse('bookmarks:index'), { - 'q': 'foo', - 'user': '', - 'sort': BookmarkSearch.SORT_ADDED_DESC, - 'shared': BookmarkSearch.FILTER_SHARED_OFF, - 'unread': BookmarkSearch.FILTER_UNREAD_YES, - }) + response = self.client.post( + reverse("bookmarks:index"), + { + "q": "foo", + "user": "", + "sort": BookmarkSearch.SORT_ADDED_DESC, + "shared": BookmarkSearch.FILTER_SHARED_OFF, + "unread": BookmarkSearch.FILTER_UNREAD_YES, + }, + ) self.assertEqual(response.status_code, 302) - self.assertEqual(response.url, reverse('bookmarks:index') + '?q=foo&unread=yes') + self.assertEqual(response.url, reverse("bookmarks:index") + "?q=foo&unread=yes") # page is removed - response = self.client.post(reverse('bookmarks:index'), { - 'q': 'foo', - 'page': '2', - 'sort': BookmarkSearch.SORT_TITLE_ASC, - }) + response = self.client.post( + reverse("bookmarks:index"), + { + "q": "foo", + "page": "2", + "sort": BookmarkSearch.SORT_TITLE_ASC, + }, + ) self.assertEqual(response.status_code, 302) - self.assertEqual(response.url, reverse('bookmarks:index') + '?q=foo&sort=title_asc') + self.assertEqual( + response.url, reverse("bookmarks:index") + "?q=foo&sort=title_asc" + ) def test_save_search_preferences(self): user_profile = self.user.profile # no params - self.client.post(reverse('bookmarks:index'), { - 'save': '', - }) + self.client.post( + reverse("bookmarks:index"), + { + "save": "", + }, + ) user_profile.refresh_from_db() - self.assertEqual(user_profile.search_preferences, { - 'sort': BookmarkSearch.SORT_ADDED_DESC, - 'shared': BookmarkSearch.FILTER_SHARED_OFF, - 'unread': BookmarkSearch.FILTER_UNREAD_OFF, - }) + self.assertEqual( + user_profile.search_preferences, + { + "sort": BookmarkSearch.SORT_ADDED_DESC, + "shared": BookmarkSearch.FILTER_SHARED_OFF, + "unread": BookmarkSearch.FILTER_UNREAD_OFF, + }, + ) # with param - self.client.post(reverse('bookmarks:index'), { - 'save': '', - 'sort': BookmarkSearch.SORT_TITLE_ASC, - }) + self.client.post( + reverse("bookmarks:index"), + { + "save": "", + "sort": BookmarkSearch.SORT_TITLE_ASC, + }, + ) user_profile.refresh_from_db() - self.assertEqual(user_profile.search_preferences, { - 'sort': BookmarkSearch.SORT_TITLE_ASC, - 'shared': BookmarkSearch.FILTER_SHARED_OFF, - 'unread': BookmarkSearch.FILTER_UNREAD_OFF, - }) + self.assertEqual( + user_profile.search_preferences, + { + "sort": BookmarkSearch.SORT_TITLE_ASC, + "shared": BookmarkSearch.FILTER_SHARED_OFF, + "unread": BookmarkSearch.FILTER_UNREAD_OFF, + }, + ) # add a param - self.client.post(reverse('bookmarks:index'), { - 'save': '', - 'sort': BookmarkSearch.SORT_TITLE_ASC, - 'unread': BookmarkSearch.FILTER_UNREAD_YES, - }) + self.client.post( + reverse("bookmarks:index"), + { + "save": "", + "sort": BookmarkSearch.SORT_TITLE_ASC, + "unread": BookmarkSearch.FILTER_UNREAD_YES, + }, + ) user_profile.refresh_from_db() - self.assertEqual(user_profile.search_preferences, { - 'sort': BookmarkSearch.SORT_TITLE_ASC, - 'shared': BookmarkSearch.FILTER_SHARED_OFF, - 'unread': BookmarkSearch.FILTER_UNREAD_YES, - }) + self.assertEqual( + user_profile.search_preferences, + { + "sort": BookmarkSearch.SORT_TITLE_ASC, + "shared": BookmarkSearch.FILTER_SHARED_OFF, + "unread": BookmarkSearch.FILTER_UNREAD_YES, + }, + ) # remove a param - self.client.post(reverse('bookmarks:index'), { - 'save': '', - 'unread': BookmarkSearch.FILTER_UNREAD_YES, - }) + self.client.post( + reverse("bookmarks:index"), + { + "save": "", + "unread": BookmarkSearch.FILTER_UNREAD_YES, + }, + ) user_profile.refresh_from_db() - self.assertEqual(user_profile.search_preferences, { - 'sort': BookmarkSearch.SORT_ADDED_DESC, - 'shared': BookmarkSearch.FILTER_SHARED_OFF, - 'unread': BookmarkSearch.FILTER_UNREAD_YES, - }) + self.assertEqual( + user_profile.search_preferences, + { + "sort": BookmarkSearch.SORT_ADDED_DESC, + "shared": BookmarkSearch.FILTER_SHARED_OFF, + "unread": BookmarkSearch.FILTER_UNREAD_YES, + }, + ) # ignores non-preferences - self.client.post(reverse('bookmarks:index'), { - 'save': '', - 'q': 'foo', - 'user': 'john', - 'page': '3', - 'sort': BookmarkSearch.SORT_TITLE_ASC, - }) + self.client.post( + reverse("bookmarks:index"), + { + "save": "", + "q": "foo", + "user": "john", + "page": "3", + "sort": BookmarkSearch.SORT_TITLE_ASC, + }, + ) user_profile.refresh_from_db() - self.assertEqual(user_profile.search_preferences, { - 'sort': BookmarkSearch.SORT_TITLE_ASC, - 'shared': BookmarkSearch.FILTER_SHARED_OFF, - 'unread': BookmarkSearch.FILTER_UNREAD_OFF, - }) + self.assertEqual( + user_profile.search_preferences, + { + "sort": BookmarkSearch.SORT_TITLE_ASC, + "shared": BookmarkSearch.FILTER_SHARED_OFF, + "unread": BookmarkSearch.FILTER_UNREAD_OFF, + }, + ) def test_url_encode_bookmark_actions_url(self): - url = reverse('bookmarks:index') + '?q=%23foo' + url = reverse("bookmarks:index") + "?q=%23foo" response = self.client.get(url) html = response.content.decode() soup = self.make_soup(html) - actions_form = soup.select('form.bookmark-actions')[0] + actions_form = soup.select("form.bookmark-actions")[0] - self.assertEqual(actions_form.attrs['action'], - '/bookmarks/action?q=%23foo&return_url=%2Fbookmarks%3Fq%3D%2523foo') + self.assertEqual( + actions_form.attrs["action"], + "/bookmarks/action?q=%23foo&return_url=%2Fbookmarks%3Fq%3D%2523foo", + ) def test_encode_search_params(self): - bookmark = self.setup_bookmark(description='alert(\'xss\')') + bookmark = self.setup_bookmark(description="alert('xss')") - url = reverse('bookmarks:index') + '?q=alert(%27xss%27)' + url = reverse("bookmarks:index") + "?q=alert(%27xss%27)" response = self.client.get(url) - self.assertNotContains(response, 'alert(\'xss\')') + self.assertNotContains(response, "alert('xss')") self.assertContains(response, bookmark.url) - url = reverse('bookmarks:index') + '?sort=alert(%27xss%27)' + url = reverse("bookmarks:index") + "?sort=alert(%27xss%27)" response = self.client.get(url) - self.assertNotContains(response, 'alert(\'xss\')') + self.assertNotContains(response, "alert('xss')") - url = reverse('bookmarks:index') + '?unread=alert(%27xss%27)' + url = reverse("bookmarks:index") + "?unread=alert(%27xss%27)" response = self.client.get(url) - self.assertNotContains(response, 'alert(\'xss\')') + self.assertNotContains(response, "alert('xss')") - url = reverse('bookmarks:index') + '?shared=alert(%27xss%27)' + url = reverse("bookmarks:index") + "?shared=alert(%27xss%27)" response = self.client.get(url) - self.assertNotContains(response, 'alert(\'xss\')') + self.assertNotContains(response, "alert('xss')") - url = reverse('bookmarks:index') + '?user=alert(%27xss%27)' + url = reverse("bookmarks:index") + "?user=alert(%27xss%27)" response = self.client.get(url) - self.assertNotContains(response, 'alert(\'xss\')') + self.assertNotContains(response, "alert('xss')") - url = reverse('bookmarks:index') + '?page=alert(%27xss%27)' + url = reverse("bookmarks:index") + "?page=alert(%27xss%27)" response = self.client.get(url) - self.assertNotContains(response, 'alert(\'xss\')') + self.assertNotContains(response, "alert('xss')") diff --git a/bookmarks/tests/test_bookmark_index_view_performance.py b/bookmarks/tests/test_bookmark_index_view_performance.py index 122b00f..ac50895 100644 --- a/bookmarks/tests/test_bookmark_index_view_performance.py +++ b/bookmarks/tests/test_bookmark_index_view_performance.py @@ -26,8 +26,10 @@ class BookmarkIndexViewPerformanceTestCase(TransactionTestCase, BookmarkFactoryM # capture number of queries context = CaptureQueriesContext(self.get_connection()) with context: - response = self.client.get(reverse('bookmarks:index')) - self.assertContains(response, '
  • ', num_initial_bookmarks) + response = self.client.get(reverse("bookmarks:index")) + self.assertContains( + response, "
  • ", num_initial_bookmarks + ) number_of_queries = context.final_queries @@ -38,5 +40,9 @@ class BookmarkIndexViewPerformanceTestCase(TransactionTestCase, BookmarkFactoryM # assert num queries doesn't increase with self.assertNumQueries(number_of_queries): - response = self.client.get(reverse('bookmarks:index')) - self.assertContains(response, '
  • ', num_initial_bookmarks + num_additional_bookmarks) + response = self.client.get(reverse("bookmarks:index")) + self.assertContains( + response, + "
  • ", + num_initial_bookmarks + num_additional_bookmarks, + ) diff --git a/bookmarks/tests/test_bookmark_new_view.py b/bookmarks/tests/test_bookmark_new_view.py index c12815e..a8ebd44 100644 --- a/bookmarks/tests/test_bookmark_new_view.py +++ b/bookmarks/tests/test_bookmark_new_view.py @@ -15,41 +15,41 @@ class BookmarkNewViewTestCase(TestCase, BookmarkFactoryMixin): if overrides is None: overrides = {} form_data = { - 'url': 'http://example.com', - 'tag_string': 'tag1 tag2', - 'title': 'test title', - 'description': 'test description', - 'notes': 'test notes', - 'unread': False, - 'shared': False, - 'auto_close': '', + "url": "http://example.com", + "tag_string": "tag1 tag2", + "title": "test title", + "description": "test description", + "notes": "test notes", + "unread": False, + "shared": False, + "auto_close": "", } return {**form_data, **overrides} def test_should_create_new_bookmark(self): form_data = self.create_form_data() - self.client.post(reverse('bookmarks:new'), form_data) + self.client.post(reverse("bookmarks:new"), form_data) self.assertEqual(Bookmark.objects.count(), 1) bookmark = Bookmark.objects.first() self.assertEqual(bookmark.owner, self.user) - self.assertEqual(bookmark.url, form_data['url']) - self.assertEqual(bookmark.title, form_data['title']) - self.assertEqual(bookmark.description, form_data['description']) - self.assertEqual(bookmark.notes, form_data['notes']) - self.assertEqual(bookmark.unread, form_data['unread']) - self.assertEqual(bookmark.shared, form_data['shared']) + self.assertEqual(bookmark.url, form_data["url"]) + self.assertEqual(bookmark.title, form_data["title"]) + self.assertEqual(bookmark.description, form_data["description"]) + self.assertEqual(bookmark.notes, form_data["notes"]) + self.assertEqual(bookmark.unread, form_data["unread"]) + self.assertEqual(bookmark.shared, form_data["shared"]) self.assertEqual(bookmark.tags.count(), 2) - tags = bookmark.tags.order_by('name').all() - self.assertEqual(tags[0].name, 'tag1') - self.assertEqual(tags[1].name, 'tag2') + tags = bookmark.tags.order_by("name").all() + self.assertEqual(tags[0].name, "tag1") + self.assertEqual(tags[1].name, "tag2") def test_should_create_new_unread_bookmark(self): - form_data = self.create_form_data({'unread': True}) + form_data = self.create_form_data({"unread": True}) - self.client.post(reverse('bookmarks:new'), form_data) + self.client.post(reverse("bookmarks:new"), form_data) self.assertEqual(Bookmark.objects.count(), 1) @@ -57,9 +57,9 @@ class BookmarkNewViewTestCase(TestCase, BookmarkFactoryMixin): self.assertTrue(bookmark.unread) def test_should_create_new_shared_bookmark(self): - form_data = self.create_form_data({'shared': True}) + form_data = self.create_form_data({"shared": True}) - self.client.post(reverse('bookmarks:new'), form_data) + self.client.post(reverse("bookmarks:new"), form_data) self.assertEqual(Bookmark.objects.count(), 1) @@ -67,124 +67,146 @@ class BookmarkNewViewTestCase(TestCase, BookmarkFactoryMixin): self.assertTrue(bookmark.shared) def test_should_prefill_url_from_url_parameter(self): - response = self.client.get(reverse('bookmarks:new') + '?url=http://example.com') + response = self.client.get(reverse("bookmarks:new") + "?url=http://example.com") html = response.content.decode() self.assertInHTML( '', - html) + html, + ) def test_should_prefill_title_from_url_parameter(self): - response = self.client.get(reverse('bookmarks:new') + '?title=Example%20Title') + response = self.client.get(reverse("bookmarks:new") + "?title=Example%20Title") html = response.content.decode() self.assertInHTML( '', - html) + html, + ) def test_should_prefill_description_from_url_parameter(self): - response = self.client.get(reverse('bookmarks:new') + '?description=Example%20Site%20Description') + response = self.client.get( + reverse("bookmarks:new") + "?description=Example%20Site%20Description" + ) html = response.content.decode() self.assertInHTML( '', - html) + html, + ) def test_should_enable_auto_close_when_specified_in_url_parameter(self): - response = self.client.get( - reverse('bookmarks:new') + '?auto_close') + response = self.client.get(reverse("bookmarks:new") + "?auto_close") html = response.content.decode() self.assertInHTML( '', - html) + html, + ) - def test_should_not_enable_auto_close_when_not_specified_in_url_parameter( - self): - response = self.client.get(reverse('bookmarks:new')) + def test_should_not_enable_auto_close_when_not_specified_in_url_parameter(self): + response = self.client.get(reverse("bookmarks:new")) html = response.content.decode() - self.assertInHTML('', html) + self.assertInHTML( + '', html + ) def test_should_redirect_to_index_view(self): form_data = self.create_form_data() - response = self.client.post(reverse('bookmarks:new'), form_data) + response = self.client.post(reverse("bookmarks:new"), form_data) - self.assertRedirects(response, reverse('bookmarks:index')) + self.assertRedirects(response, reverse("bookmarks:index")) def test_should_not_redirect_to_external_url(self): form_data = self.create_form_data() - response = self.client.post(reverse('bookmarks:new') + '?return_url=https://example.com', form_data) + response = self.client.post( + reverse("bookmarks:new") + "?return_url=https://example.com", form_data + ) - self.assertRedirects(response, reverse('bookmarks:index')) + self.assertRedirects(response, reverse("bookmarks:index")) def test_auto_close_should_redirect_to_close_view(self): - form_data = self.create_form_data({'auto_close': 'true'}) + form_data = self.create_form_data({"auto_close": "true"}) - response = self.client.post(reverse('bookmarks:new'), form_data) + response = self.client.post(reverse("bookmarks:new"), form_data) - self.assertRedirects(response, reverse('bookmarks:close')) + self.assertRedirects(response, reverse("bookmarks:close")) def test_should_respect_share_profile_setting(self): self.user.profile.enable_sharing = False self.user.profile.save() - response = self.client.get(reverse('bookmarks:new')) + response = self.client.get(reverse("bookmarks:new")) html = response.content.decode() - self.assertInHTML(''' + self.assertInHTML( + """ - ''', html, count=0) + """, + html, + count=0, + ) self.user.profile.enable_sharing = True self.user.profile.save() - response = self.client.get(reverse('bookmarks:new')) + response = self.client.get(reverse("bookmarks:new")) html = response.content.decode() - self.assertInHTML(''' + self.assertInHTML( + """ - ''', html, count=1) + """, + html, + count=1, + ) def test_should_show_respective_share_hint(self): self.user.profile.enable_sharing = True self.user.profile.save() - response = self.client.get(reverse('bookmarks:new')) + response = self.client.get(reverse("bookmarks:new")) html = response.content.decode() - self.assertInHTML(''' + self.assertInHTML( + """
    Share this bookmark with other registered users.
    - ''', html) + """, + html, + ) self.user.profile.enable_public_sharing = True self.user.profile.save() - response = self.client.get(reverse('bookmarks:new')) + response = self.client.get(reverse("bookmarks:new")) html = response.content.decode() - self.assertInHTML(''' + self.assertInHTML( + """
    Share this bookmark with other registered users and anonymous users.
    - ''', html) + """, + html, + ) def test_should_hide_notes_if_there_are_no_notes(self): bookmark = self.setup_bookmark() - response = self.client.get(reverse('bookmarks:edit', args=[bookmark.id])) + response = self.client.get(reverse("bookmarks:edit", args=[bookmark.id])) self.assertContains(response, '
    ', count=1) diff --git a/bookmarks/tests/test_bookmark_search_form.py b/bookmarks/tests/test_bookmark_search_form.py index 750b4f9..b516b6e 100644 --- a/bookmarks/tests/test_bookmark_search_form.py +++ b/bookmarks/tests/test_bookmark_search_form.py @@ -9,40 +9,45 @@ class BookmarkSearchFormTest(TestCase, BookmarkFactoryMixin): # no params search = BookmarkSearch() form = BookmarkSearchForm(search) - self.assertEqual(form['q'].initial, '') - self.assertEqual(form['user'].initial, '') - self.assertEqual(form['sort'].initial, BookmarkSearch.SORT_ADDED_DESC) - self.assertEqual(form['shared'].initial, BookmarkSearch.FILTER_SHARED_OFF) - self.assertEqual(form['unread'].initial, BookmarkSearch.FILTER_UNREAD_OFF) + self.assertEqual(form["q"].initial, "") + self.assertEqual(form["user"].initial, "") + self.assertEqual(form["sort"].initial, BookmarkSearch.SORT_ADDED_DESC) + self.assertEqual(form["shared"].initial, BookmarkSearch.FILTER_SHARED_OFF) + self.assertEqual(form["unread"].initial, BookmarkSearch.FILTER_UNREAD_OFF) # with params - search = BookmarkSearch(q='search query', - sort=BookmarkSearch.SORT_ADDED_ASC, - user='user123', - shared=BookmarkSearch.FILTER_SHARED_SHARED, - unread=BookmarkSearch.FILTER_UNREAD_YES) + search = BookmarkSearch( + q="search query", + sort=BookmarkSearch.SORT_ADDED_ASC, + user="user123", + shared=BookmarkSearch.FILTER_SHARED_SHARED, + unread=BookmarkSearch.FILTER_UNREAD_YES, + ) form = BookmarkSearchForm(search) - self.assertEqual(form['q'].initial, 'search query') - self.assertEqual(form['user'].initial, 'user123') - self.assertEqual(form['sort'].initial, BookmarkSearch.SORT_ADDED_ASC) - self.assertEqual(form['shared'].initial, BookmarkSearch.FILTER_SHARED_SHARED) - self.assertEqual(form['unread'].initial, BookmarkSearch.FILTER_UNREAD_YES) + self.assertEqual(form["q"].initial, "search query") + self.assertEqual(form["user"].initial, "user123") + self.assertEqual(form["sort"].initial, BookmarkSearch.SORT_ADDED_ASC) + self.assertEqual(form["shared"].initial, BookmarkSearch.FILTER_SHARED_SHARED) + self.assertEqual(form["unread"].initial, BookmarkSearch.FILTER_UNREAD_YES) def test_user_options(self): users = [ - self.setup_user('user1'), - self.setup_user('user2'), - self.setup_user('user3'), + self.setup_user("user1"), + self.setup_user("user2"), + self.setup_user("user3"), ] search = BookmarkSearch() form = BookmarkSearchForm(search, users=users) - self.assertCountEqual(form['user'].field.choices, [ - ('', 'Everyone'), - ('user1', 'user1'), - ('user2', 'user2'), - ('user3', 'user3'), - ]) + self.assertCountEqual( + form["user"].field.choices, + [ + ("", "Everyone"), + ("user1", "user1"), + ("user2", "user2"), + ("user3", "user3"), + ], + ) def test_hidden_fields(self): # no modified params @@ -51,24 +56,27 @@ class BookmarkSearchFormTest(TestCase, BookmarkFactoryMixin): self.assertEqual(len(form.hidden_fields()), 0) # some modified params - search = BookmarkSearch(q='search query', - sort=BookmarkSearch.SORT_ADDED_ASC) + search = BookmarkSearch(q="search query", sort=BookmarkSearch.SORT_ADDED_ASC) form = BookmarkSearchForm(search) - self.assertCountEqual(form.hidden_fields(), [form['q'], form['sort']]) + self.assertCountEqual(form.hidden_fields(), [form["q"], form["sort"]]) # all modified params - search = BookmarkSearch(q='search query', - sort=BookmarkSearch.SORT_ADDED_ASC, - user='user123', - shared=BookmarkSearch.FILTER_SHARED_SHARED, - unread=BookmarkSearch.FILTER_UNREAD_YES) + search = BookmarkSearch( + q="search query", + sort=BookmarkSearch.SORT_ADDED_ASC, + user="user123", + shared=BookmarkSearch.FILTER_SHARED_SHARED, + unread=BookmarkSearch.FILTER_UNREAD_YES, + ) form = BookmarkSearchForm(search) - self.assertCountEqual(form.hidden_fields(), - [form['q'], form['sort'], form['user'], form['shared'], form['unread']]) + self.assertCountEqual( + form.hidden_fields(), + [form["q"], form["sort"], form["user"], form["shared"], form["unread"]], + ) # some modified params are editable fields - search = BookmarkSearch(q='search query', - sort=BookmarkSearch.SORT_ADDED_ASC, - user='user123') - form = BookmarkSearchForm(search, editable_fields=['q', 'user']) - self.assertCountEqual(form.hidden_fields(), [form['sort']]) + search = BookmarkSearch( + q="search query", sort=BookmarkSearch.SORT_ADDED_ASC, user="user123" + ) + form = BookmarkSearchForm(search, editable_fields=["q", "user"]) + self.assertCountEqual(form.hidden_fields(), [form["sort"]]) diff --git a/bookmarks/tests/test_bookmark_search_model.py b/bookmarks/tests/test_bookmark_search_model.py index 3f69362..69caddf 100644 --- a/bookmarks/tests/test_bookmark_search_model.py +++ b/bookmarks/tests/test_bookmark_search_model.py @@ -10,57 +10,59 @@ class BookmarkSearchModelTest(TestCase): query_dict = QueryDict() search = BookmarkSearch.from_request(query_dict) - self.assertEqual(search.q, '') - self.assertEqual(search.user, '') + self.assertEqual(search.q, "") + self.assertEqual(search.user, "") self.assertEqual(search.sort, BookmarkSearch.SORT_ADDED_DESC) self.assertEqual(search.shared, BookmarkSearch.FILTER_SHARED_OFF) self.assertEqual(search.unread, BookmarkSearch.FILTER_UNREAD_OFF) # some params - query_dict = QueryDict('q=search query&user=user123') + query_dict = QueryDict("q=search query&user=user123") bookmark_search = BookmarkSearch.from_request(query_dict) - self.assertEqual(bookmark_search.q, 'search query') - self.assertEqual(bookmark_search.user, 'user123') + self.assertEqual(bookmark_search.q, "search query") + self.assertEqual(bookmark_search.user, "user123") self.assertEqual(bookmark_search.sort, BookmarkSearch.SORT_ADDED_DESC) self.assertEqual(search.shared, BookmarkSearch.FILTER_SHARED_OFF) self.assertEqual(search.unread, BookmarkSearch.FILTER_UNREAD_OFF) # all params - query_dict = QueryDict('q=search query&sort=title_asc&user=user123&shared=yes&unread=yes') + query_dict = QueryDict( + "q=search query&sort=title_asc&user=user123&shared=yes&unread=yes" + ) search = BookmarkSearch.from_request(query_dict) - self.assertEqual(search.q, 'search query') - self.assertEqual(search.user, 'user123') + self.assertEqual(search.q, "search query") + self.assertEqual(search.user, "user123") self.assertEqual(search.sort, BookmarkSearch.SORT_TITLE_ASC) self.assertEqual(search.shared, BookmarkSearch.FILTER_SHARED_SHARED) self.assertEqual(search.unread, BookmarkSearch.FILTER_UNREAD_YES) # respects preferences preferences = { - 'sort': BookmarkSearch.SORT_TITLE_ASC, - 'unread': BookmarkSearch.FILTER_UNREAD_YES, + "sort": BookmarkSearch.SORT_TITLE_ASC, + "unread": BookmarkSearch.FILTER_UNREAD_YES, } - query_dict = QueryDict('q=search query') + query_dict = QueryDict("q=search query") search = BookmarkSearch.from_request(query_dict, preferences) - self.assertEqual(search.q, 'search query') - self.assertEqual(search.user, '') + self.assertEqual(search.q, "search query") + self.assertEqual(search.user, "") self.assertEqual(search.sort, BookmarkSearch.SORT_TITLE_ASC) self.assertEqual(search.shared, BookmarkSearch.FILTER_SHARED_OFF) self.assertEqual(search.unread, BookmarkSearch.FILTER_UNREAD_YES) # query overrides preferences preferences = { - 'sort': BookmarkSearch.SORT_TITLE_ASC, - 'shared': BookmarkSearch.FILTER_SHARED_SHARED, - 'unread': BookmarkSearch.FILTER_UNREAD_YES, + "sort": BookmarkSearch.SORT_TITLE_ASC, + "shared": BookmarkSearch.FILTER_SHARED_SHARED, + "unread": BookmarkSearch.FILTER_UNREAD_YES, } - query_dict = QueryDict('sort=title_desc&shared=no&unread=off') + query_dict = QueryDict("sort=title_desc&shared=no&unread=off") search = BookmarkSearch.from_request(query_dict, preferences) - self.assertEqual(search.q, '') - self.assertEqual(search.user, '') + self.assertEqual(search.q, "") + self.assertEqual(search.user, "") self.assertEqual(search.sort, BookmarkSearch.SORT_TITLE_DESC) self.assertEqual(search.shared, BookmarkSearch.FILTER_SHARED_UNSHARED) self.assertEqual(search.unread, BookmarkSearch.FILTER_UNREAD_OFF) @@ -72,28 +74,36 @@ class BookmarkSearchModelTest(TestCase): self.assertEqual(len(modified_params), 0) # params are default values - bookmark_search = BookmarkSearch(q='', sort=BookmarkSearch.SORT_ADDED_DESC, user='', shared='') + bookmark_search = BookmarkSearch( + q="", sort=BookmarkSearch.SORT_ADDED_DESC, user="", shared="" + ) modified_params = bookmark_search.modified_params self.assertEqual(len(modified_params), 0) # some modified params - bookmark_search = BookmarkSearch(q='search query', sort=BookmarkSearch.SORT_ADDED_ASC) + bookmark_search = BookmarkSearch( + q="search query", sort=BookmarkSearch.SORT_ADDED_ASC + ) modified_params = bookmark_search.modified_params - self.assertCountEqual(modified_params, ['q', 'sort']) + self.assertCountEqual(modified_params, ["q", "sort"]) # all modified params - bookmark_search = BookmarkSearch(q='search query', - sort=BookmarkSearch.SORT_ADDED_ASC, - user='user123', - shared=BookmarkSearch.FILTER_SHARED_SHARED, - unread=BookmarkSearch.FILTER_UNREAD_YES) + bookmark_search = BookmarkSearch( + q="search query", + sort=BookmarkSearch.SORT_ADDED_ASC, + user="user123", + shared=BookmarkSearch.FILTER_SHARED_SHARED, + unread=BookmarkSearch.FILTER_UNREAD_YES, + ) modified_params = bookmark_search.modified_params - self.assertCountEqual(modified_params, ['q', 'sort', 'user', 'shared', 'unread']) + self.assertCountEqual( + modified_params, ["q", "sort", "user", "shared", "unread"] + ) # preferences are not modified params preferences = { - 'sort': BookmarkSearch.SORT_TITLE_ASC, - 'unread': BookmarkSearch.FILTER_UNREAD_YES, + "sort": BookmarkSearch.SORT_TITLE_ASC, + "unread": BookmarkSearch.FILTER_UNREAD_YES, } bookmark_search = BookmarkSearch(preferences=preferences) modified_params = bookmark_search.modified_params @@ -101,27 +111,31 @@ class BookmarkSearchModelTest(TestCase): # param is not modified if it matches the preference preferences = { - 'sort': BookmarkSearch.SORT_TITLE_ASC, - 'unread': BookmarkSearch.FILTER_UNREAD_YES, + "sort": BookmarkSearch.SORT_TITLE_ASC, + "unread": BookmarkSearch.FILTER_UNREAD_YES, } - bookmark_search = BookmarkSearch(sort=BookmarkSearch.SORT_TITLE_ASC, - unread=BookmarkSearch.FILTER_UNREAD_YES, - preferences=preferences) + bookmark_search = BookmarkSearch( + sort=BookmarkSearch.SORT_TITLE_ASC, + unread=BookmarkSearch.FILTER_UNREAD_YES, + preferences=preferences, + ) modified_params = bookmark_search.modified_params self.assertEqual(len(modified_params), 0) # overriding preferences is a modified param preferences = { - 'sort': BookmarkSearch.SORT_TITLE_ASC, - 'shared': BookmarkSearch.FILTER_SHARED_SHARED, - 'unread': BookmarkSearch.FILTER_UNREAD_YES, + "sort": BookmarkSearch.SORT_TITLE_ASC, + "shared": BookmarkSearch.FILTER_SHARED_SHARED, + "unread": BookmarkSearch.FILTER_UNREAD_YES, } - bookmark_search = BookmarkSearch(sort=BookmarkSearch.SORT_TITLE_DESC, - shared=BookmarkSearch.FILTER_SHARED_UNSHARED, - unread=BookmarkSearch.FILTER_UNREAD_OFF, - preferences=preferences) + bookmark_search = BookmarkSearch( + sort=BookmarkSearch.SORT_TITLE_DESC, + shared=BookmarkSearch.FILTER_SHARED_UNSHARED, + unread=BookmarkSearch.FILTER_UNREAD_OFF, + preferences=preferences, + ) modified_params = bookmark_search.modified_params - self.assertCountEqual(modified_params, ['sort', 'shared', 'unread']) + self.assertCountEqual(modified_params, ["sort", "shared", "unread"]) def test_has_modifications(self): # no params @@ -129,34 +143,49 @@ class BookmarkSearchModelTest(TestCase): self.assertFalse(bookmark_search.has_modifications) # params are default values - bookmark_search = BookmarkSearch(q='', sort=BookmarkSearch.SORT_ADDED_DESC, user='', shared='') + bookmark_search = BookmarkSearch( + q="", sort=BookmarkSearch.SORT_ADDED_DESC, user="", shared="" + ) self.assertFalse(bookmark_search.has_modifications) # modified params - bookmark_search = BookmarkSearch(q='search query', sort=BookmarkSearch.SORT_ADDED_ASC) + bookmark_search = BookmarkSearch( + q="search query", sort=BookmarkSearch.SORT_ADDED_ASC + ) self.assertTrue(bookmark_search.has_modifications) def test_preferences_dict(self): # no params bookmark_search = BookmarkSearch() - self.assertEqual(bookmark_search.preferences_dict, { - 'sort': BookmarkSearch.SORT_ADDED_DESC, - 'shared': BookmarkSearch.FILTER_SHARED_OFF, - 'unread': BookmarkSearch.FILTER_UNREAD_OFF, - }) + self.assertEqual( + bookmark_search.preferences_dict, + { + "sort": BookmarkSearch.SORT_ADDED_DESC, + "shared": BookmarkSearch.FILTER_SHARED_OFF, + "unread": BookmarkSearch.FILTER_UNREAD_OFF, + }, + ) # with params - bookmark_search = BookmarkSearch(sort=BookmarkSearch.SORT_TITLE_DESC, unread=BookmarkSearch.FILTER_UNREAD_YES) - self.assertEqual(bookmark_search.preferences_dict, { - 'sort': BookmarkSearch.SORT_TITLE_DESC, - 'shared': BookmarkSearch.FILTER_SHARED_OFF, - 'unread': BookmarkSearch.FILTER_UNREAD_YES, - }) + bookmark_search = BookmarkSearch( + sort=BookmarkSearch.SORT_TITLE_DESC, unread=BookmarkSearch.FILTER_UNREAD_YES + ) + self.assertEqual( + bookmark_search.preferences_dict, + { + "sort": BookmarkSearch.SORT_TITLE_DESC, + "shared": BookmarkSearch.FILTER_SHARED_OFF, + "unread": BookmarkSearch.FILTER_UNREAD_YES, + }, + ) # only returns preferences - bookmark_search = BookmarkSearch(q='search query', user='user123') - self.assertEqual(bookmark_search.preferences_dict, { - 'sort': BookmarkSearch.SORT_ADDED_DESC, - 'shared': BookmarkSearch.FILTER_SHARED_OFF, - 'unread': BookmarkSearch.FILTER_UNREAD_OFF, - }) + bookmark_search = BookmarkSearch(q="search query", user="user123") + self.assertEqual( + bookmark_search.preferences_dict, + { + "sort": BookmarkSearch.SORT_ADDED_DESC, + "shared": BookmarkSearch.FILTER_SHARED_OFF, + "unread": BookmarkSearch.FILTER_UNREAD_OFF, + }, + ) diff --git a/bookmarks/tests/test_bookmark_search_tag.py b/bookmarks/tests/test_bookmark_search_tag.py index a7c9a8a..d40bf47 100644 --- a/bookmarks/tests/test_bookmark_search_tag.py +++ b/bookmarks/tests/test_bookmark_search_tag.py @@ -8,21 +8,25 @@ from bookmarks.tests.helpers import BookmarkFactoryMixin, HtmlTestMixin class BookmarkSearchTagTest(TestCase, BookmarkFactoryMixin, HtmlTestMixin): - def render_template(self, url: str, tags: QuerySet[Tag] = Tag.objects.all(), mode: str = ''): + def render_template( + self, url: str, tags: QuerySet[Tag] = Tag.objects.all(), mode: str = "" + ): rf = RequestFactory() request = rf.get(url) request.user = self.get_or_create_test_user() request.user_profile = self.get_or_create_test_user().profile search = BookmarkSearch.from_request(request.GET) - context = RequestContext(request, { - 'request': request, - 'search': search, - 'tags': tags, - 'mode': mode, - }) + context = RequestContext( + request, + { + "request": request, + "search": search, + "tags": tags, + "mode": mode, + }, + ) template_to_render = Template( - '{% load bookmarks %}' - '{% bookmark_search search tags mode %}' + "{% load bookmarks %}" "{% bookmark_search search tags mode %}" ) return template_to_render.render(context) @@ -31,7 +35,7 @@ class BookmarkSearchTagTest(TestCase, BookmarkFactoryMixin, HtmlTestMixin): self.assertIsNotNone(input) if value is not None: - self.assertEqual(input['value'], value) + self.assertEqual(input["value"], value) def assertNoHiddenInput(self, form: BeautifulSoup, name: str): input = form.select_one(f'input[name="{name}"][type="hidden"]') @@ -42,19 +46,19 @@ class BookmarkSearchTagTest(TestCase, BookmarkFactoryMixin, HtmlTestMixin): self.assertIsNotNone(input) if value is not None: - self.assertEqual(input['value'], value) + self.assertEqual(input["value"], value) def assertSelect(self, form: BeautifulSoup, name: str, value: str = None): select = form.select_one(f'select[name="{name}"]') self.assertIsNotNone(select) if value is not None: - options = select.select('option') + options = select.select("option") for option in options: - if option['value'] == value: - self.assertTrue(option.has_attr('selected')) + if option["value"] == value: + self.assertTrue(option.has_attr("selected")) else: - self.assertFalse(option.has_attr('selected')) + self.assertFalse(option.has_attr("selected")) def assertRadioGroup(self, form: BeautifulSoup, name: str, value: str = None): radios = form.select(f'input[name="{name}"][type="radio"]') @@ -62,165 +66,182 @@ class BookmarkSearchTagTest(TestCase, BookmarkFactoryMixin, HtmlTestMixin): if value is not None: for radio in radios: - if radio['value'] == value: - self.assertTrue(radio.has_attr('checked')) + if radio["value"] == value: + self.assertTrue(radio.has_attr("checked")) else: - self.assertFalse(radio.has_attr('checked')) + self.assertFalse(radio.has_attr("checked")) def assertNoRadioGroup(self, form: BeautifulSoup, name: str): radios = form.select(f'input[name="{name}"][type="radio"]') self.assertTrue(len(radios) == 0) - def assertUnmodifiedLabel(self, html: str, text: str, id: str = ''): - id_attr = f'for="{id}"' if id else '' - tag = 'label' if id else 'div' + def assertUnmodifiedLabel(self, html: str, text: str, id: str = ""): + id_attr = f'for="{id}"' if id else "" + tag = "label" if id else "div" needle = f'<{tag} class="form-label" {id_attr}>{text}' self.assertInHTML(needle, html) - def assertModifiedLabel(self, html: str, text: str, id: str = ''): - id_attr = f'for="{id}"' if id else '' - tag = 'label' if id else 'div' + def assertModifiedLabel(self, html: str, text: str, id: str = ""): + id_attr = f'for="{id}"' if id else "" + tag = "label" if id else "div" needle = f'<{tag} class="form-label text-bold" {id_attr}>{text}' self.assertInHTML(needle, html) def test_search_form_inputs(self): # Without params - url = '/test' + url = "/test" rendered_template = self.render_template(url) soup = self.make_soup(rendered_template) - search_form = soup.select_one('form#search') + search_form = soup.select_one("form#search") - self.assertSearchInput(search_form, 'q') - self.assertNoHiddenInput(search_form, 'user') - self.assertNoHiddenInput(search_form, 'sort') - self.assertNoHiddenInput(search_form, 'shared') - self.assertNoHiddenInput(search_form, 'unread') + self.assertSearchInput(search_form, "q") + self.assertNoHiddenInput(search_form, "user") + self.assertNoHiddenInput(search_form, "sort") + self.assertNoHiddenInput(search_form, "shared") + self.assertNoHiddenInput(search_form, "unread") # With params - url = '/test?q=foo&user=john&sort=title_asc&shared=yes&unread=yes' + url = "/test?q=foo&user=john&sort=title_asc&shared=yes&unread=yes" rendered_template = self.render_template(url) soup = self.make_soup(rendered_template) - search_form = soup.select_one('form#search') + search_form = soup.select_one("form#search") - self.assertSearchInput(search_form, 'q', 'foo') - self.assertHiddenInput(search_form, 'user', 'john') - self.assertHiddenInput(search_form, 'sort', BookmarkSearch.SORT_TITLE_ASC) - self.assertHiddenInput(search_form, 'shared', BookmarkSearch.FILTER_SHARED_SHARED) - self.assertHiddenInput(search_form, 'unread', BookmarkSearch.FILTER_UNREAD_YES) + self.assertSearchInput(search_form, "q", "foo") + self.assertHiddenInput(search_form, "user", "john") + self.assertHiddenInput(search_form, "sort", BookmarkSearch.SORT_TITLE_ASC) + self.assertHiddenInput( + search_form, "shared", BookmarkSearch.FILTER_SHARED_SHARED + ) + self.assertHiddenInput(search_form, "unread", BookmarkSearch.FILTER_UNREAD_YES) def test_preferences_form_inputs(self): # Without params - url = '/test' + url = "/test" rendered_template = self.render_template(url) soup = self.make_soup(rendered_template) - preferences_form = soup.select_one('form#search_preferences') + preferences_form = soup.select_one("form#search_preferences") - self.assertNoHiddenInput(preferences_form, 'q') - self.assertNoHiddenInput(preferences_form, 'user') - self.assertNoHiddenInput(preferences_form, 'sort') - self.assertNoHiddenInput(preferences_form, 'shared') - self.assertNoHiddenInput(preferences_form, 'unread') + self.assertNoHiddenInput(preferences_form, "q") + self.assertNoHiddenInput(preferences_form, "user") + self.assertNoHiddenInput(preferences_form, "sort") + self.assertNoHiddenInput(preferences_form, "shared") + self.assertNoHiddenInput(preferences_form, "unread") - self.assertSelect(preferences_form, 'sort', BookmarkSearch.SORT_ADDED_DESC) - self.assertRadioGroup(preferences_form, 'shared', BookmarkSearch.FILTER_SHARED_OFF) - self.assertRadioGroup(preferences_form, 'unread', BookmarkSearch.FILTER_UNREAD_OFF) + self.assertSelect(preferences_form, "sort", BookmarkSearch.SORT_ADDED_DESC) + self.assertRadioGroup( + preferences_form, "shared", BookmarkSearch.FILTER_SHARED_OFF + ) + self.assertRadioGroup( + preferences_form, "unread", BookmarkSearch.FILTER_UNREAD_OFF + ) # With params - url = '/test?q=foo&user=john&sort=title_asc&shared=yes&unread=yes' + url = "/test?q=foo&user=john&sort=title_asc&shared=yes&unread=yes" rendered_template = self.render_template(url) soup = self.make_soup(rendered_template) - preferences_form = soup.select_one('form#search_preferences') + preferences_form = soup.select_one("form#search_preferences") - self.assertHiddenInput(preferences_form, 'q', 'foo') - self.assertHiddenInput(preferences_form, 'user', 'john') - self.assertNoHiddenInput(preferences_form, 'sort') - self.assertNoHiddenInput(preferences_form, 'shared') - self.assertNoHiddenInput(preferences_form, 'unread') + self.assertHiddenInput(preferences_form, "q", "foo") + self.assertHiddenInput(preferences_form, "user", "john") + self.assertNoHiddenInput(preferences_form, "sort") + self.assertNoHiddenInput(preferences_form, "shared") + self.assertNoHiddenInput(preferences_form, "unread") - self.assertSelect(preferences_form, 'sort', BookmarkSearch.SORT_TITLE_ASC) - self.assertRadioGroup(preferences_form, 'shared', BookmarkSearch.FILTER_SHARED_SHARED) - self.assertRadioGroup(preferences_form, 'unread', BookmarkSearch.FILTER_UNREAD_YES) + self.assertSelect(preferences_form, "sort", BookmarkSearch.SORT_TITLE_ASC) + self.assertRadioGroup( + preferences_form, "shared", BookmarkSearch.FILTER_SHARED_SHARED + ) + self.assertRadioGroup( + preferences_form, "unread", BookmarkSearch.FILTER_UNREAD_YES + ) def test_preferences_form_inputs_shared_mode(self): # Without params - url = '/test' - rendered_template = self.render_template(url, mode='shared') + url = "/test" + rendered_template = self.render_template(url, mode="shared") soup = self.make_soup(rendered_template) - preferences_form = soup.select_one('form#search_preferences') + preferences_form = soup.select_one("form#search_preferences") - self.assertNoHiddenInput(preferences_form, 'q') - self.assertNoHiddenInput(preferences_form, 'user') - self.assertNoHiddenInput(preferences_form, 'sort') - self.assertNoHiddenInput(preferences_form, 'shared') - self.assertNoHiddenInput(preferences_form, 'unread') + self.assertNoHiddenInput(preferences_form, "q") + self.assertNoHiddenInput(preferences_form, "user") + self.assertNoHiddenInput(preferences_form, "sort") + self.assertNoHiddenInput(preferences_form, "shared") + self.assertNoHiddenInput(preferences_form, "unread") - self.assertSelect(preferences_form, 'sort', BookmarkSearch.SORT_ADDED_DESC) - self.assertNoRadioGroup(preferences_form, 'shared') - self.assertNoRadioGroup(preferences_form, 'unread') + self.assertSelect(preferences_form, "sort", BookmarkSearch.SORT_ADDED_DESC) + self.assertNoRadioGroup(preferences_form, "shared") + self.assertNoRadioGroup(preferences_form, "unread") # With params - url = '/test?q=foo&user=john&sort=title_asc' - rendered_template = self.render_template(url, mode='shared') + url = "/test?q=foo&user=john&sort=title_asc" + rendered_template = self.render_template(url, mode="shared") soup = self.make_soup(rendered_template) - preferences_form = soup.select_one('form#search_preferences') + preferences_form = soup.select_one("form#search_preferences") - self.assertHiddenInput(preferences_form, 'q', 'foo') - self.assertHiddenInput(preferences_form, 'user', 'john') - self.assertNoHiddenInput(preferences_form, 'sort') - self.assertNoHiddenInput(preferences_form, 'shared') - self.assertNoHiddenInput(preferences_form, 'unread') + self.assertHiddenInput(preferences_form, "q", "foo") + self.assertHiddenInput(preferences_form, "user", "john") + self.assertNoHiddenInput(preferences_form, "sort") + self.assertNoHiddenInput(preferences_form, "shared") + self.assertNoHiddenInput(preferences_form, "unread") - self.assertSelect(preferences_form, 'sort', BookmarkSearch.SORT_TITLE_ASC) - self.assertNoRadioGroup(preferences_form, 'shared') - self.assertNoRadioGroup(preferences_form, 'unread') + self.assertSelect(preferences_form, "sort", BookmarkSearch.SORT_TITLE_ASC) + self.assertNoRadioGroup(preferences_form, "shared") + self.assertNoRadioGroup(preferences_form, "unread") def test_modified_indicator(self): # Without modifications - url = '/test' + url = "/test" rendered_template = self.render_template(url) - self.assertIn(' - ''', html, count=count) + """, + html, + count=count, + ) # Delete link - self.assertInHTML(f''' + self.assertInHTML( + f""" - ''', html, count=count) + """, + html, + count=count, + ) def assertShareInfo(self, html: str, bookmark: Bookmark): self.assertShareInfoCount(html, bookmark, 1) @@ -75,11 +101,15 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin): self.assertShareInfoCount(html, bookmark, 0) def assertShareInfoCount(self, html: str, bookmark: Bookmark, count=1): - self.assertInHTML(f''' + self.assertInHTML( + f""" Shared by {bookmark.owner.username} - ''', html, count=count) + """, + html, + count=count, + ) def assertFaviconVisible(self, html: str, bookmark: Bookmark): self.assertFaviconCount(html, bookmark, 1) @@ -88,47 +118,68 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin): self.assertFaviconCount(html, bookmark, 0) def assertFaviconCount(self, html: str, bookmark: Bookmark, count=1): - self.assertInHTML(f''' + self.assertInHTML( + f""" - ''', html, count=count) + """, + html, + count=count, + ) - def assertBookmarkURLCount(self, html: str, bookmark: Bookmark, link_target: str = '_blank', count=0): - self.assertInHTML(f''' + def assertBookmarkURLCount( + self, html: str, bookmark: Bookmark, link_target: str = "_blank", count=0 + ): + self.assertInHTML( + f""" - ''', html, count) + """, + html, + count, + ) def assertBookmarkURLVisible(self, html: str, bookmark: Bookmark): self.assertBookmarkURLCount(html, bookmark, count=1) - def assertBookmarkURLHidden(self, html: str, bookmark: Bookmark, link_target: str = '_blank'): + def assertBookmarkURLHidden( + self, html: str, bookmark: Bookmark, link_target: str = "_blank" + ): self.assertBookmarkURLCount(html, bookmark, count=0) def assertNotes(self, html: str, notes_html: str, count=1): - self.assertInHTML(f''' + self.assertInHTML( + f"""
    {notes_html}
    - ''', html, count=count) + """, + html, + count=count, + ) def assertNotesToggle(self, html: str, count=1): - self.assertInHTML(f''' + self.assertInHTML( + f""" - ''', html, count=count) + """, + html, + count=count, + ) def assertUnshareButton(self, html: str, bookmark: Bookmark, count=1): - self.assertInHTML(f''' + self.assertInHTML( + f""" - ''', html, count=count) + """, + html, + count=count, + ) def assertMarkAsReadButton(self, html: str, bookmark: Bookmark, count=1): - self.assertInHTML(f''' + self.assertInHTML( + f""" - ''', html, count=count) + """, + html, + count=count, + ) - def render_template(self, - url='/bookmarks', - context_type: Type[contexts.BookmarkListContext] = contexts.ActiveBookmarkListContext, - user: User | AnonymousUser = None) -> str: + def render_template( + self, + url="/bookmarks", + context_type: Type[ + contexts.BookmarkListContext + ] = contexts.ActiveBookmarkListContext, + user: User | AnonymousUser = None, + ) -> str: rf = RequestFactory() request = rf.get(url) request.user = user or self.get_or_create_test_user() @@ -162,14 +224,14 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin): middleware(request) bookmark_list_context = context_type(request) - context = RequestContext(request, {'bookmark_list': bookmark_list_context}) + context = RequestContext(request, {"bookmark_list": bookmark_list_context}) - template = Template( - "{% include 'bookmarks/bookmark_list.html' %}" - ) + template = Template("{% include 'bookmarks/bookmark_list.html' %}") return template.render(context) - def setup_date_format_test(self, date_display_setting: str, web_archive_url: str = ''): + def setup_date_format_test( + self, date_display_setting: str, web_archive_url: str = "" + ): bookmark = self.setup_bookmark() bookmark.date_added = timezone.now() - relativedelta(days=8) bookmark.web_archive_snapshot_url = web_archive_url @@ -180,38 +242,46 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin): return bookmark def test_should_respect_absolute_date_setting(self): - bookmark = self.setup_date_format_test(UserProfile.BOOKMARK_DATE_DISPLAY_ABSOLUTE) + bookmark = self.setup_date_format_test( + UserProfile.BOOKMARK_DATE_DISPLAY_ABSOLUTE + ) html = self.render_template() - formatted_date = formats.date_format(bookmark.date_added, 'SHORT_DATE_FORMAT') + formatted_date = formats.date_format(bookmark.date_added, "SHORT_DATE_FORMAT") self.assertDateLabel(html, formatted_date) def test_should_render_web_archive_link_with_absolute_date_setting(self): - bookmark = self.setup_date_format_test(UserProfile.BOOKMARK_DATE_DISPLAY_ABSOLUTE, - 'https://web.archive.org/web/20210811214511/https://wanikani.com/') + bookmark = self.setup_date_format_test( + UserProfile.BOOKMARK_DATE_DISPLAY_ABSOLUTE, + "https://web.archive.org/web/20210811214511/https://wanikani.com/", + ) html = self.render_template() - formatted_date = formats.date_format(bookmark.date_added, 'SHORT_DATE_FORMAT') + formatted_date = formats.date_format(bookmark.date_added, "SHORT_DATE_FORMAT") - self.assertWebArchiveLink(html, formatted_date, bookmark.web_archive_snapshot_url) + self.assertWebArchiveLink( + html, formatted_date, bookmark.web_archive_snapshot_url + ) def test_should_respect_relative_date_setting(self): self.setup_date_format_test(UserProfile.BOOKMARK_DATE_DISPLAY_RELATIVE) html = self.render_template() - self.assertDateLabel(html, '1 week ago') + self.assertDateLabel(html, "1 week ago") def test_should_render_web_archive_link_with_relative_date_setting(self): - bookmark = self.setup_date_format_test(UserProfile.BOOKMARK_DATE_DISPLAY_RELATIVE, - 'https://web.archive.org/web/20210811214511/https://wanikani.com/') + bookmark = self.setup_date_format_test( + UserProfile.BOOKMARK_DATE_DISPLAY_RELATIVE, + "https://web.archive.org/web/20210811214511/https://wanikani.com/", + ) html = self.render_template() - self.assertWebArchiveLink(html, '1 week ago', bookmark.web_archive_snapshot_url) + self.assertWebArchiveLink(html, "1 week ago", bookmark.web_archive_snapshot_url) def test_bookmark_link_target_should_be_blank_by_default(self): bookmark = self.setup_bookmark() html = self.render_template() - self.assertBookmarksLink(html, bookmark, link_target='_blank') + self.assertBookmarksLink(html, bookmark, link_target="_blank") def test_bookmark_link_target_should_respect_user_profile(self): profile = self.get_or_create_test_user().profile @@ -221,17 +291,19 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin): bookmark = self.setup_bookmark() html = self.render_template() - self.assertBookmarksLink(html, bookmark, link_target='_self') + self.assertBookmarksLink(html, bookmark, link_target="_self") def test_web_archive_link_target_should_be_blank_by_default(self): bookmark = self.setup_bookmark() bookmark.date_added = timezone.now() - relativedelta(days=8) - bookmark.web_archive_snapshot_url = 'https://example.com' + bookmark.web_archive_snapshot_url = "https://example.com" bookmark.save() html = self.render_template() - self.assertWebArchiveLink(html, '1 week ago', bookmark.web_archive_snapshot_url, link_target='_blank') + self.assertWebArchiveLink( + html, "1 week ago", bookmark.web_archive_snapshot_url, link_target="_blank" + ) def test_web_archive_link_target_should_respect_user_profile(self): profile = self.get_or_create_test_user().profile @@ -240,12 +312,14 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin): bookmark = self.setup_bookmark() bookmark.date_added = timezone.now() - relativedelta(days=8) - bookmark.web_archive_snapshot_url = 'https://example.com' + bookmark.web_archive_snapshot_url = "https://example.com" bookmark.save() html = self.render_template() - self.assertWebArchiveLink(html, '1 week ago', bookmark.web_archive_snapshot_url, link_target='_self') + self.assertWebArchiveLink( + html, "1 week ago", bookmark.web_archive_snapshot_url, link_target="_self" + ) def test_should_reflect_unread_state_as_css_class(self): self.setup_bookmark(unread=True) @@ -281,7 +355,9 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin): self.assertNoShareInfo(html, bookmark) def test_show_share_info_for_non_owned_bookmarks(self): - other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123') + other_user = User.objects.create_user( + "otheruser", "otheruser@example.com", "password123" + ) other_user.profile.enable_sharing = True other_user.profile.save() @@ -292,25 +368,32 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin): self.assertShareInfo(html, bookmark) def test_share_info_user_link_keeps_query_params(self): - other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123') + other_user = User.objects.create_user( + "otheruser", "otheruser@example.com", "password123" + ) other_user.profile.enable_sharing = True other_user.profile.save() - bookmark = self.setup_bookmark(user=other_user, shared=True, title='foo') - html = self.render_template(url='/bookmarks?q=foo', context_type=contexts.SharedBookmarkListContext) + bookmark = self.setup_bookmark(user=other_user, shared=True, title="foo") + html = self.render_template( + url="/bookmarks?q=foo", context_type=contexts.SharedBookmarkListContext + ) - self.assertInHTML(f''' + self.assertInHTML( + f""" Shared by {bookmark.owner.username} - ''', html) + """, + html, + ) def test_favicon_should_be_visible_when_favicons_enabled(self): profile = self.get_or_create_test_user().profile profile.enable_favicons = True profile.save() - bookmark = self.setup_bookmark(favicon_file='https_example_com.png') + bookmark = self.setup_bookmark(favicon_file="https_example_com.png") html = self.render_template() self.assertFaviconVisible(html, bookmark) @@ -320,7 +403,7 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin): profile.enable_favicons = True profile.save() - bookmark = self.setup_bookmark(favicon_file='') + bookmark = self.setup_bookmark(favicon_file="") html = self.render_template() self.assertFaviconHidden(html, bookmark) @@ -330,7 +413,7 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin): profile.enable_favicons = False profile.save() - bookmark = self.setup_bookmark(favicon_file='https_example_com.png') + bookmark = self.setup_bookmark(favicon_file="https_example_com.png") html = self.render_template() self.assertFaviconHidden(html, bookmark) @@ -428,21 +511,23 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin): self.setup_bookmark() html = self.render_template() - self.assertNotes(html, '', 0) + self.assertNotes(html, "", 0) self.assertNotesToggle(html, 0) def test_with_notes(self): - self.setup_bookmark(notes='Test note') + self.setup_bookmark(notes="Test note") html = self.render_template() - note_html = '

    Test note

    ' + note_html = "

    Test note

    " self.assertNotes(html, note_html, 1) def test_note_renders_markdown(self): self.setup_bookmark(notes='**Example:** `print("Hello world!")`') html = self.render_template() - note_html = '

    Example: print("Hello world!")

    ' + note_html = ( + '

    Example: print("Hello world!")

    ' + ) self.assertNotes(html, note_html, 1) def test_note_cleans_html(self): @@ -453,7 +538,7 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin): self.assertNotes(html, note_html, 1) def test_notes_are_hidden_initially_by_default(self): - self.setup_bookmark(notes='Test note') + self.setup_bookmark(notes="Test note") html = collapse_whitespace(self.render_template()) self.assertIn('
      ', html) @@ -463,7 +548,7 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin): profile.permanent_notes = False profile.save() - self.setup_bookmark(notes='Test note') + self.setup_bookmark(notes="Test note") html = collapse_whitespace(self.render_template()) self.assertIn('
        ', html) @@ -473,13 +558,15 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin): profile.permanent_notes = True profile.save() - self.setup_bookmark(notes='Test note') + self.setup_bookmark(notes="Test note") html = collapse_whitespace(self.render_template()) - self.assertIn('
          ', html) + self.assertIn( + '
            ', html + ) def test_toggle_notes_is_visible_by_default(self): - self.setup_bookmark(notes='Test note') + self.setup_bookmark(notes="Test note") html = self.render_template() self.assertNotesToggle(html, 1) @@ -489,7 +576,7 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin): profile.permanent_notes = False profile.save() - self.setup_bookmark(notes='Test note') + self.setup_bookmark(notes="Test note") html = self.render_template() self.assertNotesToggle(html, 1) @@ -499,7 +586,7 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin): profile.permanent_notes = True profile.save() - self.setup_bookmark(notes='Test note') + self.setup_bookmark(notes="Test note") html = self.render_template() self.assertNotesToggle(html, 0) @@ -512,25 +599,35 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin): bookmark = self.setup_bookmark() bookmark.date_added = timezone.now() - relativedelta(days=8) - bookmark.web_archive_snapshot_url = 'https://web.archive.org/web/20230531200136/https://example.com' + bookmark.web_archive_snapshot_url = ( + "https://web.archive.org/web/20230531200136/https://example.com" + ) bookmark.notes = '**Example:** `print("Hello world!")`' - bookmark.favicon_file = 'https_example_com.png' + bookmark.favicon_file = "https_example_com.png" bookmark.shared = True bookmark.unread = True bookmark.save() - html = self.render_template(context_type=contexts.SharedBookmarkListContext, user=AnonymousUser()) - self.assertBookmarksLink(html, bookmark, link_target='_blank') - self.assertWebArchiveLink(html, '1 week ago', bookmark.web_archive_snapshot_url, link_target='_blank') + html = self.render_template( + context_type=contexts.SharedBookmarkListContext, user=AnonymousUser() + ) + self.assertBookmarksLink(html, bookmark, link_target="_blank") + self.assertWebArchiveLink( + html, "1 week ago", bookmark.web_archive_snapshot_url, link_target="_blank" + ) self.assertNoBookmarkActions(html, bookmark) self.assertShareInfo(html, bookmark) self.assertMarkAsReadButton(html, bookmark, count=0) self.assertUnshareButton(html, bookmark, count=0) - note_html = '

            Example: print("Hello world!")

            ' + note_html = ( + '

            Example: print("Hello world!")

            ' + ) self.assertNotes(html, note_html, 1) self.assertFaviconVisible(html, bookmark) def test_empty_state(self): html = self.render_template() - self.assertInHTML('

            You have no bookmarks yet

            ', html) + self.assertInHTML( + '

            You have no bookmarks yet

            ', html + ) diff --git a/bookmarks/tests/test_bookmarks_model.py b/bookmarks/tests/test_bookmarks_model.py index 0ca9775..f4c18b7 100644 --- a/bookmarks/tests/test_bookmarks_model.py +++ b/bookmarks/tests/test_bookmarks_model.py @@ -6,11 +6,17 @@ from bookmarks.models import Bookmark class BookmarkTestCase(TestCase): def test_bookmark_resolved_title(self): - bookmark = Bookmark(title='Custom title', website_title='Website title', url='https://example.com') - self.assertEqual(bookmark.resolved_title, 'Custom title') + bookmark = Bookmark( + title="Custom title", + website_title="Website title", + url="https://example.com", + ) + self.assertEqual(bookmark.resolved_title, "Custom title") - bookmark = Bookmark(title='', website_title='Website title', url='https://example.com') - self.assertEqual(bookmark.resolved_title, 'Website title') + bookmark = Bookmark( + title="", website_title="Website title", url="https://example.com" + ) + self.assertEqual(bookmark.resolved_title, "Website title") - bookmark = Bookmark(title='', website_title='', url='https://example.com') - self.assertEqual(bookmark.resolved_title, 'https://example.com') + bookmark = Bookmark(title="", website_title="", url="https://example.com") + self.assertEqual(bookmark.resolved_title, "https://example.com") diff --git a/bookmarks/tests/test_bookmarks_service.py b/bookmarks/tests/test_bookmarks_service.py index 96b3675..c6d121f 100644 --- a/bookmarks/tests/test_bookmarks_service.py +++ b/bookmarks/tests/test_bookmarks_service.py @@ -7,9 +7,21 @@ from django.utils import timezone from bookmarks.models import Bookmark, Tag from bookmarks.services import tasks from bookmarks.services import website_loader -from bookmarks.services.bookmarks import create_bookmark, update_bookmark, archive_bookmark, archive_bookmarks, \ - unarchive_bookmark, unarchive_bookmarks, delete_bookmarks, tag_bookmarks, untag_bookmarks, mark_bookmarks_as_read, \ - mark_bookmarks_as_unread, share_bookmarks, unshare_bookmarks +from bookmarks.services.bookmarks import ( + create_bookmark, + update_bookmark, + archive_bookmark, + archive_bookmarks, + unarchive_bookmark, + unarchive_bookmarks, + delete_bookmarks, + tag_bookmarks, + untag_bookmarks, + mark_bookmarks_as_read, + mark_bookmarks_as_unread, + share_bookmarks, + unshare_bookmarks, +) from bookmarks.services.website_loader import WebsiteMetadata from bookmarks.tests.helpers import BookmarkFactoryMixin @@ -22,36 +34,48 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin): self.get_or_create_test_user() def test_create_should_update_website_metadata(self): - with patch.object(website_loader, 'load_website_metadata') as mock_load_website_metadata: + with patch.object( + website_loader, "load_website_metadata" + ) as mock_load_website_metadata: expected_metadata = WebsiteMetadata( - 'https://example.com', - 'Website title', - 'Website description' + "https://example.com", "Website title", "Website description" ) mock_load_website_metadata.return_value = expected_metadata - bookmark_data = Bookmark(url='https://example.com', - title='Updated Title', - description='Updated description', - unread=True, - shared=True, - is_archived=True) - created_bookmark = create_bookmark(bookmark_data, '', self.get_or_create_test_user()) + bookmark_data = Bookmark( + url="https://example.com", + title="Updated Title", + description="Updated description", + unread=True, + shared=True, + is_archived=True, + ) + created_bookmark = create_bookmark( + bookmark_data, "", self.get_or_create_test_user() + ) created_bookmark.refresh_from_db() self.assertEqual(expected_metadata.title, created_bookmark.website_title) - self.assertEqual(expected_metadata.description, created_bookmark.website_description) + self.assertEqual( + expected_metadata.description, created_bookmark.website_description + ) def test_create_should_update_existing_bookmark_with_same_url(self): - original_bookmark = self.setup_bookmark(url='https://example.com', unread=False, shared=False) - bookmark_data = Bookmark(url='https://example.com', - title='Updated Title', - description='Updated description', - notes='Updated notes', - unread=True, - shared=True, - is_archived=True) - updated_bookmark = create_bookmark(bookmark_data, '', self.get_or_create_test_user()) + original_bookmark = self.setup_bookmark( + url="https://example.com", unread=False, shared=False + ) + bookmark_data = Bookmark( + url="https://example.com", + title="Updated Title", + description="Updated description", + notes="Updated notes", + unread=True, + shared=True, + is_archived=True, + ) + updated_bookmark = create_bookmark( + bookmark_data, "", self.get_or_create_test_user() + ) self.assertEqual(Bookmark.objects.count(), 1) self.assertEqual(updated_bookmark.id, original_bookmark.id) @@ -64,75 +88,91 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin): self.assertFalse(updated_bookmark.is_archived) def test_create_should_create_web_archive_snapshot(self): - with patch.object(tasks, 'create_web_archive_snapshot') as mock_create_web_archive_snapshot: - bookmark_data = Bookmark(url='https://example.com') - bookmark = create_bookmark(bookmark_data, 'tag1,tag2', self.user) + with patch.object( + tasks, "create_web_archive_snapshot" + ) as mock_create_web_archive_snapshot: + bookmark_data = Bookmark(url="https://example.com") + bookmark = create_bookmark(bookmark_data, "tag1,tag2", self.user) - mock_create_web_archive_snapshot.assert_called_once_with(self.user, bookmark, False) + mock_create_web_archive_snapshot.assert_called_once_with( + self.user, bookmark, False + ) def test_create_should_load_favicon(self): - with patch.object(tasks, 'load_favicon') as mock_load_favicon: - bookmark_data = Bookmark(url='https://example.com') - bookmark = create_bookmark(bookmark_data, 'tag1,tag2', self.user) + with patch.object(tasks, "load_favicon") as mock_load_favicon: + bookmark_data = Bookmark(url="https://example.com") + bookmark = create_bookmark(bookmark_data, "tag1,tag2", self.user) mock_load_favicon.assert_called_once_with(self.user, bookmark) def test_update_should_create_web_archive_snapshot_if_url_did_change(self): - with patch.object(tasks, 'create_web_archive_snapshot') as mock_create_web_archive_snapshot: + with patch.object( + tasks, "create_web_archive_snapshot" + ) as mock_create_web_archive_snapshot: bookmark = self.setup_bookmark() - bookmark.url = 'https://example.com/updated' - update_bookmark(bookmark, 'tag1,tag2', self.user) + bookmark.url = "https://example.com/updated" + update_bookmark(bookmark, "tag1,tag2", self.user) - mock_create_web_archive_snapshot.assert_called_once_with(self.user, bookmark, True) + mock_create_web_archive_snapshot.assert_called_once_with( + self.user, bookmark, True + ) def test_update_should_not_create_web_archive_snapshot_if_url_did_not_change(self): - with patch.object(tasks, 'create_web_archive_snapshot') as mock_create_web_archive_snapshot: + with patch.object( + tasks, "create_web_archive_snapshot" + ) as mock_create_web_archive_snapshot: bookmark = self.setup_bookmark() - bookmark.title = 'updated title' - update_bookmark(bookmark, 'tag1,tag2', self.user) + bookmark.title = "updated title" + update_bookmark(bookmark, "tag1,tag2", self.user) mock_create_web_archive_snapshot.assert_not_called() def test_update_should_update_website_metadata_if_url_did_change(self): - with patch.object(website_loader, 'load_website_metadata') as mock_load_website_metadata: + with patch.object( + website_loader, "load_website_metadata" + ) as mock_load_website_metadata: expected_metadata = WebsiteMetadata( - 'https://example.com/updated', - 'Updated website title', - 'Updated website description' + "https://example.com/updated", + "Updated website title", + "Updated website description", ) mock_load_website_metadata.return_value = expected_metadata bookmark = self.setup_bookmark() - bookmark.url = 'https://example.com/updated' - update_bookmark(bookmark, 'tag1,tag2', self.user) + bookmark.url = "https://example.com/updated" + update_bookmark(bookmark, "tag1,tag2", self.user) bookmark.refresh_from_db() mock_load_website_metadata.assert_called_once() self.assertEqual(expected_metadata.title, bookmark.website_title) - self.assertEqual(expected_metadata.description, bookmark.website_description) + self.assertEqual( + expected_metadata.description, bookmark.website_description + ) def test_update_should_not_update_website_metadata_if_url_did_not_change(self): - with patch.object(website_loader, 'load_website_metadata') as mock_load_website_metadata: + with patch.object( + website_loader, "load_website_metadata" + ) as mock_load_website_metadata: bookmark = self.setup_bookmark() - bookmark.title = 'updated title' - update_bookmark(bookmark, 'tag1,tag2', self.user) + bookmark.title = "updated title" + update_bookmark(bookmark, "tag1,tag2", self.user) mock_load_website_metadata.assert_not_called() def test_update_should_update_favicon(self): - with patch.object(tasks, 'load_favicon') as mock_load_favicon: + with patch.object(tasks, "load_favicon") as mock_load_favicon: bookmark = self.setup_bookmark() - bookmark.title = 'updated title' - update_bookmark(bookmark, 'tag1,tag2', self.user) + bookmark.title = "updated title" + update_bookmark(bookmark, "tag1,tag2", self.user) mock_load_favicon.assert_called_once_with(self.user, bookmark) def test_archive_bookmark(self): bookmark = Bookmark( - url='https://example.com', + url="https://example.com", date_added=timezone.now(), date_modified=timezone.now(), - owner=self.user + owner=self.user, ) bookmark.save() @@ -146,7 +186,7 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin): def test_unarchive_bookmark(self): bookmark = Bookmark( - url='https://example.com', + url="https://example.com", date_added=timezone.now(), date_modified=timezone.now(), owner=self.user, @@ -165,7 +205,9 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin): bookmark2 = self.setup_bookmark() bookmark3 = self.setup_bookmark() - archive_bookmarks([bookmark1.id, bookmark2.id, bookmark3.id], self.get_or_create_test_user()) + archive_bookmarks( + [bookmark1.id, bookmark2.id, bookmark3.id], self.get_or_create_test_user() + ) self.assertTrue(Bookmark.objects.get(id=bookmark1.id).is_archived) self.assertTrue(Bookmark.objects.get(id=bookmark2.id).is_archived) @@ -183,12 +225,17 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin): self.assertTrue(Bookmark.objects.get(id=bookmark3.id).is_archived) def test_archive_bookmarks_should_only_archive_user_owned_bookmarks(self): - other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123') + other_user = User.objects.create_user( + "otheruser", "otheruser@example.com", "password123" + ) bookmark1 = self.setup_bookmark() bookmark2 = self.setup_bookmark() inaccessible_bookmark = self.setup_bookmark(user=other_user) - archive_bookmarks([bookmark1.id, bookmark2.id, inaccessible_bookmark.id], self.get_or_create_test_user()) + archive_bookmarks( + [bookmark1.id, bookmark2.id, inaccessible_bookmark.id], + self.get_or_create_test_user(), + ) self.assertTrue(Bookmark.objects.get(id=bookmark1.id).is_archived) self.assertTrue(Bookmark.objects.get(id=bookmark2.id).is_archived) @@ -199,7 +246,10 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin): bookmark2 = self.setup_bookmark() bookmark3 = self.setup_bookmark() - archive_bookmarks([str(bookmark1.id), bookmark2.id, str(bookmark3.id)], self.get_or_create_test_user()) + archive_bookmarks( + [str(bookmark1.id), bookmark2.id, str(bookmark3.id)], + self.get_or_create_test_user(), + ) self.assertTrue(Bookmark.objects.get(id=bookmark1.id).is_archived) self.assertTrue(Bookmark.objects.get(id=bookmark2.id).is_archived) @@ -210,7 +260,9 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin): bookmark2 = self.setup_bookmark(is_archived=True) bookmark3 = self.setup_bookmark(is_archived=True) - unarchive_bookmarks([bookmark1.id, bookmark2.id, bookmark3.id], self.get_or_create_test_user()) + unarchive_bookmarks( + [bookmark1.id, bookmark2.id, bookmark3.id], self.get_or_create_test_user() + ) self.assertFalse(Bookmark.objects.get(id=bookmark1.id).is_archived) self.assertFalse(Bookmark.objects.get(id=bookmark2.id).is_archived) @@ -221,19 +273,26 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin): bookmark2 = self.setup_bookmark(is_archived=True) bookmark3 = self.setup_bookmark(is_archived=True) - unarchive_bookmarks([bookmark1.id, bookmark3.id], self.get_or_create_test_user()) + unarchive_bookmarks( + [bookmark1.id, bookmark3.id], self.get_or_create_test_user() + ) self.assertFalse(Bookmark.objects.get(id=bookmark1.id).is_archived) self.assertTrue(Bookmark.objects.get(id=bookmark2.id).is_archived) self.assertFalse(Bookmark.objects.get(id=bookmark3.id).is_archived) def test_unarchive_bookmarks_should_only_unarchive_user_owned_bookmarks(self): - other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123') + other_user = User.objects.create_user( + "otheruser", "otheruser@example.com", "password123" + ) bookmark1 = self.setup_bookmark(is_archived=True) bookmark2 = self.setup_bookmark(is_archived=True) inaccessible_bookmark = self.setup_bookmark(is_archived=True, user=other_user) - unarchive_bookmarks([bookmark1.id, bookmark2.id, inaccessible_bookmark.id], self.get_or_create_test_user()) + unarchive_bookmarks( + [bookmark1.id, bookmark2.id, inaccessible_bookmark.id], + self.get_or_create_test_user(), + ) self.assertFalse(Bookmark.objects.get(id=bookmark1.id).is_archived) self.assertFalse(Bookmark.objects.get(id=bookmark2.id).is_archived) @@ -244,7 +303,10 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin): bookmark2 = self.setup_bookmark(is_archived=True) bookmark3 = self.setup_bookmark(is_archived=True) - unarchive_bookmarks([str(bookmark1.id), bookmark2.id, str(bookmark3.id)], self.get_or_create_test_user()) + unarchive_bookmarks( + [str(bookmark1.id), bookmark2.id, str(bookmark3.id)], + self.get_or_create_test_user(), + ) self.assertFalse(Bookmark.objects.get(id=bookmark1.id).is_archived) self.assertFalse(Bookmark.objects.get(id=bookmark2.id).is_archived) @@ -255,7 +317,9 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin): bookmark2 = self.setup_bookmark() bookmark3 = self.setup_bookmark() - delete_bookmarks([bookmark1.id, bookmark2.id, bookmark3.id], self.get_or_create_test_user()) + delete_bookmarks( + [bookmark1.id, bookmark2.id, bookmark3.id], self.get_or_create_test_user() + ) self.assertIsNone(Bookmark.objects.filter(id=bookmark1.id).first()) self.assertIsNone(Bookmark.objects.filter(id=bookmark2.id).first()) @@ -273,23 +337,32 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin): self.assertIsNone(Bookmark.objects.filter(id=bookmark3.id).first()) def test_delete_bookmarks_should_only_delete_user_owned_bookmarks(self): - other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123') + other_user = User.objects.create_user( + "otheruser", "otheruser@example.com", "password123" + ) bookmark1 = self.setup_bookmark() bookmark2 = self.setup_bookmark() inaccessible_bookmark = self.setup_bookmark(user=other_user) - delete_bookmarks([bookmark1.id, bookmark2.id, inaccessible_bookmark.id], self.get_or_create_test_user()) + delete_bookmarks( + [bookmark1.id, bookmark2.id, inaccessible_bookmark.id], + self.get_or_create_test_user(), + ) self.assertIsNone(Bookmark.objects.filter(id=bookmark1.id).first()) self.assertIsNone(Bookmark.objects.filter(id=bookmark2.id).first()) - self.assertIsNotNone(Bookmark.objects.filter(id=inaccessible_bookmark.id).first()) + self.assertIsNotNone( + Bookmark.objects.filter(id=inaccessible_bookmark.id).first() + ) def test_delete_bookmarks_should_accept_mix_of_int_and_string_ids(self): bookmark1 = self.setup_bookmark() bookmark2 = self.setup_bookmark() bookmark3 = self.setup_bookmark() - delete_bookmarks([bookmark1.id, bookmark2.id, bookmark3.id], self.get_or_create_test_user()) + delete_bookmarks( + [bookmark1.id, bookmark2.id, bookmark3.id], self.get_or_create_test_user() + ) self.assertIsNone(Bookmark.objects.filter(id=bookmark1.id).first()) self.assertIsNone(Bookmark.objects.filter(id=bookmark2.id).first()) @@ -302,8 +375,11 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin): tag1 = self.setup_tag() tag2 = self.setup_tag() - tag_bookmarks([bookmark1.id, bookmark2.id, bookmark3.id], f'{tag1.name},{tag2.name}', - self.get_or_create_test_user()) + tag_bookmarks( + [bookmark1.id, bookmark2.id, bookmark3.id], + f"{tag1.name},{tag2.name}", + self.get_or_create_test_user(), + ) bookmark1.refresh_from_db() bookmark2.refresh_from_db() @@ -318,7 +394,11 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin): bookmark2 = self.setup_bookmark() bookmark3 = self.setup_bookmark() - tag_bookmarks([bookmark1.id, bookmark2.id, bookmark3.id], 'tag1,tag2', self.get_or_create_test_user()) + tag_bookmarks( + [bookmark1.id, bookmark2.id, bookmark3.id], + "tag1,tag2", + self.get_or_create_test_user(), + ) bookmark1.refresh_from_db() bookmark2.refresh_from_db() @@ -326,8 +406,8 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin): self.assertEqual(2, Tag.objects.count()) - tag1 = Tag.objects.filter(name='tag1').first() - tag2 = Tag.objects.filter(name='tag2').first() + tag1 = Tag.objects.filter(name="tag1").first() + tag2 = Tag.objects.filter(name="tag2").first() self.assertIsNotNone(tag1) self.assertIsNotNone(tag2) @@ -346,8 +426,11 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin): BookmarkToTagRelationShip = Bookmark.tags.through self.assertEqual(3, BookmarkToTagRelationShip.objects.count()) - tag_bookmarks([bookmark1.id, bookmark2.id, bookmark3.id], f'{tag1.name},{tag2.name}', - self.get_or_create_test_user()) + tag_bookmarks( + [bookmark1.id, bookmark2.id, bookmark3.id], + f"{tag1.name},{tag2.name}", + self.get_or_create_test_user(), + ) bookmark1.refresh_from_db() bookmark2.refresh_from_db() @@ -365,7 +448,11 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin): tag1 = self.setup_tag() tag2 = self.setup_tag() - tag_bookmarks([bookmark1.id, bookmark3.id], f'{tag1.name},{tag2.name}', self.get_or_create_test_user()) + tag_bookmarks( + [bookmark1.id, bookmark3.id], + f"{tag1.name},{tag2.name}", + self.get_or_create_test_user(), + ) bookmark1.refresh_from_db() bookmark2.refresh_from_db() @@ -376,15 +463,20 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin): self.assertCountEqual(bookmark3.tags.all(), [tag1, tag2]) def test_tag_bookmarks_should_only_tag_user_owned_bookmarks(self): - other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123') + other_user = User.objects.create_user( + "otheruser", "otheruser@example.com", "password123" + ) bookmark1 = self.setup_bookmark() bookmark2 = self.setup_bookmark() inaccessible_bookmark = self.setup_bookmark(user=other_user) tag1 = self.setup_tag() tag2 = self.setup_tag() - tag_bookmarks([bookmark1.id, bookmark2.id, inaccessible_bookmark.id], f'{tag1.name},{tag2.name}', - self.get_or_create_test_user()) + tag_bookmarks( + [bookmark1.id, bookmark2.id, inaccessible_bookmark.id], + f"{tag1.name},{tag2.name}", + self.get_or_create_test_user(), + ) bookmark1.refresh_from_db() bookmark2.refresh_from_db() @@ -401,8 +493,11 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin): tag1 = self.setup_tag() tag2 = self.setup_tag() - tag_bookmarks([str(bookmark1.id), bookmark2.id, str(bookmark3.id)], f'{tag1.name},{tag2.name}', - self.get_or_create_test_user()) + tag_bookmarks( + [str(bookmark1.id), bookmark2.id, str(bookmark3.id)], + f"{tag1.name},{tag2.name}", + self.get_or_create_test_user(), + ) self.assertCountEqual(bookmark1.tags.all(), [tag1, tag2]) self.assertCountEqual(bookmark2.tags.all(), [tag1, tag2]) @@ -415,8 +510,11 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin): bookmark2 = self.setup_bookmark(tags=[tag1, tag2]) bookmark3 = self.setup_bookmark(tags=[tag1, tag2]) - untag_bookmarks([bookmark1.id, bookmark2.id, bookmark3.id], f'{tag1.name},{tag2.name}', - self.get_or_create_test_user()) + untag_bookmarks( + [bookmark1.id, bookmark2.id, bookmark3.id], + f"{tag1.name},{tag2.name}", + self.get_or_create_test_user(), + ) bookmark1.refresh_from_db() bookmark2.refresh_from_db() @@ -433,7 +531,11 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin): bookmark2 = self.setup_bookmark(tags=[tag1, tag2]) bookmark3 = self.setup_bookmark(tags=[tag1, tag2]) - untag_bookmarks([bookmark1.id, bookmark3.id], f'{tag1.name},{tag2.name}', self.get_or_create_test_user()) + untag_bookmarks( + [bookmark1.id, bookmark3.id], + f"{tag1.name},{tag2.name}", + self.get_or_create_test_user(), + ) bookmark1.refresh_from_db() bookmark2.refresh_from_db() @@ -444,15 +546,20 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin): self.assertCountEqual(bookmark3.tags.all(), []) def test_untag_bookmarks_should_only_tag_user_owned_bookmarks(self): - other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123') + other_user = User.objects.create_user( + "otheruser", "otheruser@example.com", "password123" + ) tag1 = self.setup_tag() tag2 = self.setup_tag() bookmark1 = self.setup_bookmark(tags=[tag1, tag2]) bookmark2 = self.setup_bookmark(tags=[tag1, tag2]) inaccessible_bookmark = self.setup_bookmark(user=other_user, tags=[tag1, tag2]) - untag_bookmarks([bookmark1.id, bookmark2.id, inaccessible_bookmark.id], f'{tag1.name},{tag2.name}', - self.get_or_create_test_user()) + untag_bookmarks( + [bookmark1.id, bookmark2.id, inaccessible_bookmark.id], + f"{tag1.name},{tag2.name}", + self.get_or_create_test_user(), + ) bookmark1.refresh_from_db() bookmark2.refresh_from_db() @@ -469,8 +576,11 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin): bookmark2 = self.setup_bookmark(tags=[tag1, tag2]) bookmark3 = self.setup_bookmark(tags=[tag1, tag2]) - untag_bookmarks([str(bookmark1.id), bookmark2.id, str(bookmark3.id)], f'{tag1.name},{tag2.name}', - self.get_or_create_test_user()) + untag_bookmarks( + [str(bookmark1.id), bookmark2.id, str(bookmark3.id)], + f"{tag1.name},{tag2.name}", + self.get_or_create_test_user(), + ) self.assertCountEqual(bookmark1.tags.all(), []) self.assertCountEqual(bookmark2.tags.all(), []) @@ -481,7 +591,9 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin): bookmark2 = self.setup_bookmark(unread=True) bookmark3 = self.setup_bookmark(unread=True) - mark_bookmarks_as_read([bookmark1.id, bookmark2.id, bookmark3.id], self.get_or_create_test_user()) + mark_bookmarks_as_read( + [bookmark1.id, bookmark2.id, bookmark3.id], self.get_or_create_test_user() + ) self.assertFalse(Bookmark.objects.get(id=bookmark1.id).unread) self.assertFalse(Bookmark.objects.get(id=bookmark2.id).unread) @@ -492,19 +604,26 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin): bookmark2 = self.setup_bookmark(unread=True) bookmark3 = self.setup_bookmark(unread=True) - mark_bookmarks_as_read([bookmark1.id, bookmark3.id], self.get_or_create_test_user()) + mark_bookmarks_as_read( + [bookmark1.id, bookmark3.id], self.get_or_create_test_user() + ) self.assertFalse(Bookmark.objects.get(id=bookmark1.id).unread) self.assertTrue(Bookmark.objects.get(id=bookmark2.id).unread) self.assertFalse(Bookmark.objects.get(id=bookmark3.id).unread) def test_mark_bookmarks_as_read_should_only_update_user_owned_bookmarks(self): - other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123') + other_user = User.objects.create_user( + "otheruser", "otheruser@example.com", "password123" + ) bookmark1 = self.setup_bookmark(unread=True) bookmark2 = self.setup_bookmark(unread=True) inaccessible_bookmark = self.setup_bookmark(unread=True, user=other_user) - mark_bookmarks_as_read([bookmark1.id, bookmark2.id, inaccessible_bookmark.id], self.get_or_create_test_user()) + mark_bookmarks_as_read( + [bookmark1.id, bookmark2.id, inaccessible_bookmark.id], + self.get_or_create_test_user(), + ) self.assertFalse(Bookmark.objects.get(id=bookmark1.id).unread) self.assertFalse(Bookmark.objects.get(id=bookmark2.id).unread) @@ -515,7 +634,10 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin): bookmark2 = self.setup_bookmark(unread=True) bookmark3 = self.setup_bookmark(unread=True) - mark_bookmarks_as_read([str(bookmark1.id), bookmark2.id, str(bookmark3.id)], self.get_or_create_test_user()) + mark_bookmarks_as_read( + [str(bookmark1.id), bookmark2.id, str(bookmark3.id)], + self.get_or_create_test_user(), + ) self.assertFalse(Bookmark.objects.get(id=bookmark1.id).unread) self.assertFalse(Bookmark.objects.get(id=bookmark2.id).unread) @@ -526,7 +648,9 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin): bookmark2 = self.setup_bookmark(unread=False) bookmark3 = self.setup_bookmark(unread=False) - mark_bookmarks_as_unread([bookmark1.id, bookmark2.id, bookmark3.id], self.get_or_create_test_user()) + mark_bookmarks_as_unread( + [bookmark1.id, bookmark2.id, bookmark3.id], self.get_or_create_test_user() + ) self.assertTrue(Bookmark.objects.get(id=bookmark1.id).unread) self.assertTrue(Bookmark.objects.get(id=bookmark2.id).unread) @@ -537,19 +661,26 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin): bookmark2 = self.setup_bookmark(unread=False) bookmark3 = self.setup_bookmark(unread=False) - mark_bookmarks_as_unread([bookmark1.id, bookmark3.id], self.get_or_create_test_user()) + mark_bookmarks_as_unread( + [bookmark1.id, bookmark3.id], self.get_or_create_test_user() + ) self.assertTrue(Bookmark.objects.get(id=bookmark1.id).unread) self.assertFalse(Bookmark.objects.get(id=bookmark2.id).unread) self.assertTrue(Bookmark.objects.get(id=bookmark3.id).unread) def test_mark_bookmarks_as_unread_should_only_update_user_owned_bookmarks(self): - other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123') + other_user = User.objects.create_user( + "otheruser", "otheruser@example.com", "password123" + ) bookmark1 = self.setup_bookmark(unread=False) bookmark2 = self.setup_bookmark(unread=False) inaccessible_bookmark = self.setup_bookmark(unread=False, user=other_user) - mark_bookmarks_as_unread([bookmark1.id, bookmark2.id, inaccessible_bookmark.id], self.get_or_create_test_user()) + mark_bookmarks_as_unread( + [bookmark1.id, bookmark2.id, inaccessible_bookmark.id], + self.get_or_create_test_user(), + ) self.assertTrue(Bookmark.objects.get(id=bookmark1.id).unread) self.assertTrue(Bookmark.objects.get(id=bookmark2.id).unread) @@ -560,7 +691,10 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin): bookmark2 = self.setup_bookmark(unread=False) bookmark3 = self.setup_bookmark(unread=False) - mark_bookmarks_as_unread([str(bookmark1.id), bookmark2.id, str(bookmark3.id)], self.get_or_create_test_user()) + mark_bookmarks_as_unread( + [str(bookmark1.id), bookmark2.id, str(bookmark3.id)], + self.get_or_create_test_user(), + ) self.assertTrue(Bookmark.objects.get(id=bookmark1.id).unread) self.assertTrue(Bookmark.objects.get(id=bookmark2.id).unread) @@ -571,7 +705,9 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin): bookmark2 = self.setup_bookmark(shared=False) bookmark3 = self.setup_bookmark(shared=False) - share_bookmarks([bookmark1.id, bookmark2.id, bookmark3.id], self.get_or_create_test_user()) + share_bookmarks( + [bookmark1.id, bookmark2.id, bookmark3.id], self.get_or_create_test_user() + ) self.assertTrue(Bookmark.objects.get(id=bookmark1.id).shared) self.assertTrue(Bookmark.objects.get(id=bookmark2.id).shared) @@ -589,12 +725,17 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin): self.assertTrue(Bookmark.objects.get(id=bookmark3.id).shared) def test_share_bookmarks_should_only_update_user_owned_bookmarks(self): - other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123') + other_user = User.objects.create_user( + "otheruser", "otheruser@example.com", "password123" + ) bookmark1 = self.setup_bookmark(shared=False) bookmark2 = self.setup_bookmark(shared=False) inaccessible_bookmark = self.setup_bookmark(shared=False, user=other_user) - share_bookmarks([bookmark1.id, bookmark2.id, inaccessible_bookmark.id], self.get_or_create_test_user()) + share_bookmarks( + [bookmark1.id, bookmark2.id, inaccessible_bookmark.id], + self.get_or_create_test_user(), + ) self.assertTrue(Bookmark.objects.get(id=bookmark1.id).shared) self.assertTrue(Bookmark.objects.get(id=bookmark2.id).shared) @@ -605,7 +746,10 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin): bookmark2 = self.setup_bookmark(shared=False) bookmark3 = self.setup_bookmark(shared=False) - share_bookmarks([str(bookmark1.id), bookmark2.id, str(bookmark3.id)], self.get_or_create_test_user()) + share_bookmarks( + [str(bookmark1.id), bookmark2.id, str(bookmark3.id)], + self.get_or_create_test_user(), + ) self.assertTrue(Bookmark.objects.get(id=bookmark1.id).shared) self.assertTrue(Bookmark.objects.get(id=bookmark2.id).shared) @@ -616,7 +760,9 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin): bookmark2 = self.setup_bookmark(shared=True) bookmark3 = self.setup_bookmark(shared=True) - unshare_bookmarks([bookmark1.id, bookmark2.id, bookmark3.id], self.get_or_create_test_user()) + unshare_bookmarks( + [bookmark1.id, bookmark2.id, bookmark3.id], self.get_or_create_test_user() + ) self.assertFalse(Bookmark.objects.get(id=bookmark1.id).shared) self.assertFalse(Bookmark.objects.get(id=bookmark2.id).shared) @@ -634,12 +780,17 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin): self.assertFalse(Bookmark.objects.get(id=bookmark3.id).shared) def test_unshare_bookmarks_should_only_update_user_owned_bookmarks(self): - other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123') + other_user = User.objects.create_user( + "otheruser", "otheruser@example.com", "password123" + ) bookmark1 = self.setup_bookmark(shared=True) bookmark2 = self.setup_bookmark(shared=True) inaccessible_bookmark = self.setup_bookmark(shared=True, user=other_user) - unshare_bookmarks([bookmark1.id, bookmark2.id, inaccessible_bookmark.id], self.get_or_create_test_user()) + unshare_bookmarks( + [bookmark1.id, bookmark2.id, inaccessible_bookmark.id], + self.get_or_create_test_user(), + ) self.assertFalse(Bookmark.objects.get(id=bookmark1.id).shared) self.assertFalse(Bookmark.objects.get(id=bookmark2.id).shared) @@ -650,7 +801,10 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin): bookmark2 = self.setup_bookmark(shared=True) bookmark3 = self.setup_bookmark(shared=True) - unshare_bookmarks([str(bookmark1.id), bookmark2.id, str(bookmark3.id)], self.get_or_create_test_user()) + unshare_bookmarks( + [str(bookmark1.id), bookmark2.id, str(bookmark3.id)], + self.get_or_create_test_user(), + ) self.assertFalse(Bookmark.objects.get(id=bookmark1.id).shared) self.assertFalse(Bookmark.objects.get(id=bookmark2.id).shared) diff --git a/bookmarks/tests/test_bookmarks_tasks.py b/bookmarks/tests/test_bookmarks_tasks.py index 7ee9e79..f286911 100644 --- a/bookmarks/tests/test_bookmarks_tasks.py +++ b/bookmarks/tests/test_bookmarks_tasks.py @@ -16,8 +16,10 @@ from bookmarks.services import tasks from bookmarks.tests.helpers import BookmarkFactoryMixin, disable_logging -def create_wayback_machine_save_api_mock(archive_url: str = 'https://example.com/created_snapshot', - fail_on_save: bool = False): +def create_wayback_machine_save_api_mock( + archive_url: str = "https://example.com/created_snapshot", + fail_on_save: bool = False, +): mock_api = mock.Mock(archive_url=archive_url) if fail_on_save: @@ -32,14 +34,18 @@ class MockCdxSnapshot: datetime_timestamp: datetime.datetime -def create_cdx_server_api_mock(archive_url: str | None = 'https://example.com/newest_snapshot', - fail_loading_snapshot=False): +def create_cdx_server_api_mock( + archive_url: str | None = "https://example.com/newest_snapshot", + fail_loading_snapshot=False, +): mock_api = mock.Mock() if fail_loading_snapshot: mock_api.newest.side_effect = WaybackError elif archive_url: - mock_api.newest.return_value = MockCdxSnapshot(archive_url, datetime.datetime.now()) + mock_api.newest.return_value = MockCdxSnapshot( + archive_url, datetime.datetime.now() + ) else: mock_api.newest.return_value = None @@ -50,13 +56,15 @@ class BookmarkTasksTestCase(TestCase, BookmarkFactoryMixin): def setUp(self): user = self.get_or_create_test_user() - user.profile.web_archive_integration = UserProfile.WEB_ARCHIVE_INTEGRATION_ENABLED + user.profile.web_archive_integration = ( + UserProfile.WEB_ARCHIVE_INTEGRATION_ENABLED + ) user.profile.enable_favicons = True user.profile.save() @disable_logging def run_pending_task(self, task_function: Any): - func = getattr(task_function, 'task_function', None) + func = getattr(task_function, "task_function", None) task = Task.objects.all()[0] self.assertEqual(task_function.name, task.task_name) args, kwargs = task.params() @@ -65,7 +73,7 @@ class BookmarkTasksTestCase(TestCase, BookmarkFactoryMixin): @disable_logging def run_all_pending_tasks(self, task_function: Any): - func = getattr(task_function, 'task_function', None) + func = getattr(task_function, "task_function", None) tasks = Task.objects.all() for task in tasks: @@ -78,86 +86,129 @@ class BookmarkTasksTestCase(TestCase, BookmarkFactoryMixin): bookmark = self.setup_bookmark() mock_save_api = create_wayback_machine_save_api_mock() - with mock.patch.object(waybackpy, 'WaybackMachineSaveAPI', return_value=mock_save_api): - tasks.create_web_archive_snapshot(self.get_or_create_test_user(), bookmark, False) + with mock.patch.object( + waybackpy, "WaybackMachineSaveAPI", return_value=mock_save_api + ): + tasks.create_web_archive_snapshot( + self.get_or_create_test_user(), bookmark, False + ) self.run_pending_task(tasks._create_web_archive_snapshot_task) bookmark.refresh_from_db() mock_save_api.save.assert_called_once() - self.assertEqual(bookmark.web_archive_snapshot_url, 'https://example.com/created_snapshot') + self.assertEqual( + bookmark.web_archive_snapshot_url, + "https://example.com/created_snapshot", + ) def test_create_web_archive_snapshot_should_handle_missing_bookmark_id(self): mock_save_api = create_wayback_machine_save_api_mock() - with mock.patch.object(waybackpy, 'WaybackMachineSaveAPI', return_value=mock_save_api): + with mock.patch.object( + waybackpy, "WaybackMachineSaveAPI", return_value=mock_save_api + ): tasks._create_web_archive_snapshot_task(123, False) self.run_pending_task(tasks._create_web_archive_snapshot_task) mock_save_api.save.assert_not_called() def test_create_web_archive_snapshot_should_skip_if_snapshot_exists(self): - bookmark = self.setup_bookmark(web_archive_snapshot_url='https://example.com') + bookmark = self.setup_bookmark(web_archive_snapshot_url="https://example.com") mock_save_api = create_wayback_machine_save_api_mock() - with mock.patch.object(waybackpy, 'WaybackMachineSaveAPI', return_value=mock_save_api): - tasks.create_web_archive_snapshot(self.get_or_create_test_user(), bookmark, False) + with mock.patch.object( + waybackpy, "WaybackMachineSaveAPI", return_value=mock_save_api + ): + tasks.create_web_archive_snapshot( + self.get_or_create_test_user(), bookmark, False + ) self.run_pending_task(tasks._create_web_archive_snapshot_task) mock_save_api.assert_not_called() def test_create_web_archive_snapshot_should_force_update_snapshot(self): - bookmark = self.setup_bookmark(web_archive_snapshot_url='https://example.com') - mock_save_api = create_wayback_machine_save_api_mock(archive_url='https://other.com') + bookmark = self.setup_bookmark(web_archive_snapshot_url="https://example.com") + mock_save_api = create_wayback_machine_save_api_mock( + archive_url="https://other.com" + ) - with mock.patch.object(waybackpy, 'WaybackMachineSaveAPI', return_value=mock_save_api): - tasks.create_web_archive_snapshot(self.get_or_create_test_user(), bookmark, True) + with mock.patch.object( + waybackpy, "WaybackMachineSaveAPI", return_value=mock_save_api + ): + tasks.create_web_archive_snapshot( + self.get_or_create_test_user(), bookmark, True + ) self.run_pending_task(tasks._create_web_archive_snapshot_task) bookmark.refresh_from_db() - self.assertEqual(bookmark.web_archive_snapshot_url, 'https://other.com') + self.assertEqual(bookmark.web_archive_snapshot_url, "https://other.com") def test_create_web_archive_snapshot_should_use_newest_snapshot_as_fallback(self): bookmark = self.setup_bookmark() mock_save_api = create_wayback_machine_save_api_mock(fail_on_save=True) mock_cdx_api = create_cdx_server_api_mock() - with mock.patch.object(waybackpy, 'WaybackMachineSaveAPI', return_value=mock_save_api): - with mock.patch.object(bookmarks.services.wayback, 'CustomWaybackMachineCDXServerAPI', - return_value=mock_cdx_api): - tasks.create_web_archive_snapshot(self.get_or_create_test_user(), bookmark, False) + with mock.patch.object( + waybackpy, "WaybackMachineSaveAPI", return_value=mock_save_api + ): + with mock.patch.object( + bookmarks.services.wayback, + "CustomWaybackMachineCDXServerAPI", + return_value=mock_cdx_api, + ): + tasks.create_web_archive_snapshot( + self.get_or_create_test_user(), bookmark, False + ) self.run_pending_task(tasks._create_web_archive_snapshot_task) bookmark.refresh_from_db() mock_cdx_api.newest.assert_called_once() - self.assertEqual('https://example.com/newest_snapshot', bookmark.web_archive_snapshot_url) + self.assertEqual( + "https://example.com/newest_snapshot", + bookmark.web_archive_snapshot_url, + ) def test_create_web_archive_snapshot_should_ignore_missing_newest_snapshot(self): bookmark = self.setup_bookmark() mock_save_api = create_wayback_machine_save_api_mock(fail_on_save=True) mock_cdx_api = create_cdx_server_api_mock(archive_url=None) - with mock.patch.object(waybackpy, 'WaybackMachineSaveAPI', return_value=mock_save_api): - with mock.patch.object(bookmarks.services.wayback, 'CustomWaybackMachineCDXServerAPI', - return_value=mock_cdx_api): - tasks.create_web_archive_snapshot(self.get_or_create_test_user(), bookmark, False) + with mock.patch.object( + waybackpy, "WaybackMachineSaveAPI", return_value=mock_save_api + ): + with mock.patch.object( + bookmarks.services.wayback, + "CustomWaybackMachineCDXServerAPI", + return_value=mock_cdx_api, + ): + tasks.create_web_archive_snapshot( + self.get_or_create_test_user(), bookmark, False + ) self.run_pending_task(tasks._create_web_archive_snapshot_task) bookmark.refresh_from_db() - self.assertEqual('', bookmark.web_archive_snapshot_url) + self.assertEqual("", bookmark.web_archive_snapshot_url) def test_create_web_archive_snapshot_should_ignore_newest_snapshot_errors(self): bookmark = self.setup_bookmark() mock_save_api = create_wayback_machine_save_api_mock(fail_on_save=True) mock_cdx_api = create_cdx_server_api_mock(fail_loading_snapshot=True) - with mock.patch.object(waybackpy, 'WaybackMachineSaveAPI', return_value=mock_save_api): - with mock.patch.object(bookmarks.services.wayback, 'CustomWaybackMachineCDXServerAPI', - return_value=mock_cdx_api): - tasks.create_web_archive_snapshot(self.get_or_create_test_user(), bookmark, False) + with mock.patch.object( + waybackpy, "WaybackMachineSaveAPI", return_value=mock_save_api + ): + with mock.patch.object( + bookmarks.services.wayback, + "CustomWaybackMachineCDXServerAPI", + return_value=mock_cdx_api, + ): + tasks.create_web_archive_snapshot( + self.get_or_create_test_user(), bookmark, False + ) self.run_pending_task(tasks._create_web_archive_snapshot_task) bookmark.refresh_from_db() - self.assertEqual('', bookmark.web_archive_snapshot_url) + self.assertEqual("", bookmark.web_archive_snapshot_url) def test_create_web_archive_snapshot_should_not_save_stale_bookmark_data(self): bookmark = self.setup_bookmark() @@ -166,48 +217,66 @@ class BookmarkTasksTestCase(TestCase, BookmarkFactoryMixin): # update bookmark during API call to check that saving # the snapshot does not overwrite updated bookmark data def mock_save_impl(): - bookmark.title = 'Updated title' + bookmark.title = "Updated title" bookmark.save() mock_save_api.save.side_effect = mock_save_impl - with mock.patch.object(waybackpy, 'WaybackMachineSaveAPI', return_value=mock_save_api): - tasks.create_web_archive_snapshot(self.get_or_create_test_user(), bookmark, False) + with mock.patch.object( + waybackpy, "WaybackMachineSaveAPI", return_value=mock_save_api + ): + tasks.create_web_archive_snapshot( + self.get_or_create_test_user(), bookmark, False + ) self.run_pending_task(tasks._create_web_archive_snapshot_task) bookmark.refresh_from_db() - self.assertEqual(bookmark.title, 'Updated title') - self.assertEqual('https://example.com/created_snapshot', bookmark.web_archive_snapshot_url) + self.assertEqual(bookmark.title, "Updated title") + self.assertEqual( + "https://example.com/created_snapshot", + bookmark.web_archive_snapshot_url, + ) def test_load_web_archive_snapshot_should_update_snapshot_url(self): bookmark = self.setup_bookmark() mock_cdx_api = create_cdx_server_api_mock() - with mock.patch.object(bookmarks.services.wayback, 'CustomWaybackMachineCDXServerAPI', - return_value=mock_cdx_api): + with mock.patch.object( + bookmarks.services.wayback, + "CustomWaybackMachineCDXServerAPI", + return_value=mock_cdx_api, + ): tasks._load_web_archive_snapshot_task(bookmark.id) self.run_pending_task(tasks._load_web_archive_snapshot_task) bookmark.refresh_from_db() mock_cdx_api.newest.assert_called_once() - self.assertEqual('https://example.com/newest_snapshot', bookmark.web_archive_snapshot_url) + self.assertEqual( + "https://example.com/newest_snapshot", bookmark.web_archive_snapshot_url + ) def test_load_web_archive_snapshot_should_handle_missing_bookmark_id(self): mock_cdx_api = create_cdx_server_api_mock() - with mock.patch.object(bookmarks.services.wayback, 'CustomWaybackMachineCDXServerAPI', - return_value=mock_cdx_api): + with mock.patch.object( + bookmarks.services.wayback, + "CustomWaybackMachineCDXServerAPI", + return_value=mock_cdx_api, + ): tasks._load_web_archive_snapshot_task(123) self.run_pending_task(tasks._load_web_archive_snapshot_task) mock_cdx_api.newest.assert_not_called() def test_load_web_archive_snapshot_should_skip_if_snapshot_exists(self): - bookmark = self.setup_bookmark(web_archive_snapshot_url='https://example.com') + bookmark = self.setup_bookmark(web_archive_snapshot_url="https://example.com") mock_cdx_api = create_cdx_server_api_mock() - with mock.patch.object(bookmarks.services.wayback, 'CustomWaybackMachineCDXServerAPI', - return_value=mock_cdx_api): + with mock.patch.object( + bookmarks.services.wayback, + "CustomWaybackMachineCDXServerAPI", + return_value=mock_cdx_api, + ): tasks._load_web_archive_snapshot_task(bookmark.id) self.run_pending_task(tasks._load_web_archive_snapshot_task) @@ -217,23 +286,29 @@ class BookmarkTasksTestCase(TestCase, BookmarkFactoryMixin): bookmark = self.setup_bookmark() mock_cdx_api = create_cdx_server_api_mock(archive_url=None) - with mock.patch.object(bookmarks.services.wayback, 'CustomWaybackMachineCDXServerAPI', - return_value=mock_cdx_api): + with mock.patch.object( + bookmarks.services.wayback, + "CustomWaybackMachineCDXServerAPI", + return_value=mock_cdx_api, + ): tasks._load_web_archive_snapshot_task(bookmark.id) self.run_pending_task(tasks._load_web_archive_snapshot_task) - self.assertEqual('', bookmark.web_archive_snapshot_url) + self.assertEqual("", bookmark.web_archive_snapshot_url) def test_load_web_archive_snapshot_should_handle_wayback_errors(self): bookmark = self.setup_bookmark() mock_cdx_api = create_cdx_server_api_mock(fail_loading_snapshot=True) - with mock.patch.object(bookmarks.services.wayback, 'CustomWaybackMachineCDXServerAPI', - return_value=mock_cdx_api): + with mock.patch.object( + bookmarks.services.wayback, + "CustomWaybackMachineCDXServerAPI", + return_value=mock_cdx_api, + ): tasks._load_web_archive_snapshot_task(bookmark.id) self.run_pending_task(tasks._load_web_archive_snapshot_task) - self.assertEqual('', bookmark.web_archive_snapshot_url) + self.assertEqual("", bookmark.web_archive_snapshot_url) def test_load_web_archive_snapshot_should_not_save_stale_bookmark_data(self): bookmark = self.setup_bookmark() @@ -242,45 +317,62 @@ class BookmarkTasksTestCase(TestCase, BookmarkFactoryMixin): # update bookmark during API call to check that saving # the snapshot does not overwrite updated bookmark data def mock_newest_impl(): - bookmark.title = 'Updated title' + bookmark.title = "Updated title" bookmark.save() return mock.DEFAULT mock_cdx_api.newest.side_effect = mock_newest_impl - with mock.patch.object(bookmarks.services.wayback, 'CustomWaybackMachineCDXServerAPI', - return_value=mock_cdx_api): + with mock.patch.object( + bookmarks.services.wayback, + "CustomWaybackMachineCDXServerAPI", + return_value=mock_cdx_api, + ): tasks._load_web_archive_snapshot_task(bookmark.id) self.run_pending_task(tasks._load_web_archive_snapshot_task) bookmark.refresh_from_db() - self.assertEqual('Updated title', bookmark.title) - self.assertEqual('https://example.com/newest_snapshot', bookmark.web_archive_snapshot_url) + self.assertEqual("Updated title", bookmark.title) + self.assertEqual( + "https://example.com/newest_snapshot", bookmark.web_archive_snapshot_url + ) @override_settings(LD_DISABLE_BACKGROUND_TASKS=True) - def test_create_web_archive_snapshot_should_not_run_when_background_tasks_are_disabled(self): + def test_create_web_archive_snapshot_should_not_run_when_background_tasks_are_disabled( + self, + ): bookmark = self.setup_bookmark() - tasks.create_web_archive_snapshot(self.get_or_create_test_user(), bookmark, False) + tasks.create_web_archive_snapshot( + self.get_or_create_test_user(), bookmark, False + ) self.assertEqual(Task.objects.count(), 0) - def test_create_web_archive_snapshot_should_not_run_when_web_archive_integration_is_disabled(self): - self.user.profile.web_archive_integration = UserProfile.WEB_ARCHIVE_INTEGRATION_DISABLED + def test_create_web_archive_snapshot_should_not_run_when_web_archive_integration_is_disabled( + self, + ): + self.user.profile.web_archive_integration = ( + UserProfile.WEB_ARCHIVE_INTEGRATION_DISABLED + ) self.user.profile.save() bookmark = self.setup_bookmark() - tasks.create_web_archive_snapshot(self.get_or_create_test_user(), bookmark, False) + tasks.create_web_archive_snapshot( + self.get_or_create_test_user(), bookmark, False + ) self.assertEqual(Task.objects.count(), 0) - def test_schedule_bookmarks_without_snapshots_should_load_snapshot_for_all_bookmarks_without_snapshot(self): + def test_schedule_bookmarks_without_snapshots_should_load_snapshot_for_all_bookmarks_without_snapshot( + self, + ): user = self.get_or_create_test_user() self.setup_bookmark() self.setup_bookmark() self.setup_bookmark() - self.setup_bookmark(web_archive_snapshot_url='https://example.com') - self.setup_bookmark(web_archive_snapshot_url='https://example.com') - self.setup_bookmark(web_archive_snapshot_url='https://example.com') + self.setup_bookmark(web_archive_snapshot_url="https://example.com") + self.setup_bookmark(web_archive_snapshot_url="https://example.com") + self.setup_bookmark(web_archive_snapshot_url="https://example.com") tasks.schedule_bookmarks_without_snapshots(user) self.run_pending_task(tasks._schedule_bookmarks_without_snapshots_task) @@ -289,11 +381,18 @@ class BookmarkTasksTestCase(TestCase, BookmarkFactoryMixin): self.assertEqual(task_list.count(), 3) for task in task_list: - self.assertEqual(task.task_name, 'bookmarks.services.tasks._load_web_archive_snapshot_task') + self.assertEqual( + task.task_name, + "bookmarks.services.tasks._load_web_archive_snapshot_task", + ) - def test_schedule_bookmarks_without_snapshots_should_only_update_user_owned_bookmarks(self): + def test_schedule_bookmarks_without_snapshots_should_only_update_user_owned_bookmarks( + self, + ): user = self.get_or_create_test_user() - other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123') + other_user = User.objects.create_user( + "otheruser", "otheruser@example.com", "password123" + ) self.setup_bookmark() self.setup_bookmark() self.setup_bookmark() @@ -308,13 +407,19 @@ class BookmarkTasksTestCase(TestCase, BookmarkFactoryMixin): self.assertEqual(task_list.count(), 3) @override_settings(LD_DISABLE_BACKGROUND_TASKS=True) - def test_schedule_bookmarks_without_snapshots_should_not_run_when_background_tasks_are_disabled(self): + def test_schedule_bookmarks_without_snapshots_should_not_run_when_background_tasks_are_disabled( + self, + ): tasks.schedule_bookmarks_without_snapshots(self.user) self.assertEqual(Task.objects.count(), 0) - def test_schedule_bookmarks_without_snapshots_should_not_run_when_web_archive_integration_is_disabled(self): - self.user.profile.web_archive_integration = UserProfile.WEB_ARCHIVE_INTEGRATION_DISABLED + def test_schedule_bookmarks_without_snapshots_should_not_run_when_web_archive_integration_is_disabled( + self, + ): + self.user.profile.web_archive_integration = ( + UserProfile.WEB_ARCHIVE_INTEGRATION_DISABLED + ) self.user.profile.save() tasks.schedule_bookmarks_without_snapshots(self.user) @@ -323,29 +428,35 @@ class BookmarkTasksTestCase(TestCase, BookmarkFactoryMixin): def test_load_favicon_should_create_favicon_file(self): bookmark = self.setup_bookmark() - with mock.patch('bookmarks.services.favicon_loader.load_favicon') as mock_load_favicon: - mock_load_favicon.return_value = 'https_example_com.png' + with mock.patch( + "bookmarks.services.favicon_loader.load_favicon" + ) as mock_load_favicon: + mock_load_favicon.return_value = "https_example_com.png" tasks.load_favicon(self.get_or_create_test_user(), bookmark) self.run_pending_task(tasks._load_favicon_task) bookmark.refresh_from_db() - self.assertEqual(bookmark.favicon_file, 'https_example_com.png') + self.assertEqual(bookmark.favicon_file, "https_example_com.png") def test_load_favicon_should_update_favicon_file(self): - bookmark = self.setup_bookmark(favicon_file='https_example_com.png') + bookmark = self.setup_bookmark(favicon_file="https_example_com.png") - with mock.patch('bookmarks.services.favicon_loader.load_favicon') as mock_load_favicon: - mock_load_favicon.return_value = 'https_example_updated_com.png' + with mock.patch( + "bookmarks.services.favicon_loader.load_favicon" + ) as mock_load_favicon: + mock_load_favicon.return_value = "https_example_updated_com.png" tasks.load_favicon(self.get_or_create_test_user(), bookmark) self.run_pending_task(tasks._load_favicon_task) mock_load_favicon.assert_called() bookmark.refresh_from_db() - self.assertEqual(bookmark.favicon_file, 'https_example_updated_com.png') + self.assertEqual(bookmark.favicon_file, "https_example_updated_com.png") def test_load_favicon_should_handle_missing_bookmark(self): - with mock.patch('bookmarks.services.favicon_loader.load_favicon') as mock_load_favicon: + with mock.patch( + "bookmarks.services.favicon_loader.load_favicon" + ) as mock_load_favicon: tasks._load_favicon_task(123) self.run_pending_task(tasks._load_favicon_task) @@ -357,19 +468,21 @@ class BookmarkTasksTestCase(TestCase, BookmarkFactoryMixin): # update bookmark during API call to check that saving # the favicon does not overwrite updated bookmark data def mock_load_favicon_impl(url): - bookmark.title = 'Updated title' + bookmark.title = "Updated title" bookmark.save() - return 'https_example_com.png' + return "https_example_com.png" - with mock.patch('bookmarks.services.favicon_loader.load_favicon') as mock_load_favicon: + with mock.patch( + "bookmarks.services.favicon_loader.load_favicon" + ) as mock_load_favicon: mock_load_favicon.side_effect = mock_load_favicon_impl tasks.load_favicon(self.get_or_create_test_user(), bookmark) self.run_pending_task(tasks._load_favicon_task) bookmark.refresh_from_db() - self.assertEqual(bookmark.title, 'Updated title') - self.assertEqual(bookmark.favicon_file, 'https_example_com.png') + self.assertEqual(bookmark.title, "Updated title") + self.assertEqual(bookmark.favicon_file, "https_example_com.png") @override_settings(LD_DISABLE_BACKGROUND_TASKS=True) def test_load_favicon_should_not_run_when_background_tasks_are_disabled(self): @@ -387,14 +500,16 @@ class BookmarkTasksTestCase(TestCase, BookmarkFactoryMixin): self.assertEqual(Task.objects.count(), 0) - def test_schedule_bookmarks_without_favicons_should_load_favicon_for_all_bookmarks_without_favicon(self): + def test_schedule_bookmarks_without_favicons_should_load_favicon_for_all_bookmarks_without_favicon( + self, + ): user = self.get_or_create_test_user() self.setup_bookmark() self.setup_bookmark() self.setup_bookmark() - self.setup_bookmark(favicon_file='https_example_com.png') - self.setup_bookmark(favicon_file='https_example_com.png') - self.setup_bookmark(favicon_file='https_example_com.png') + self.setup_bookmark(favicon_file="https_example_com.png") + self.setup_bookmark(favicon_file="https_example_com.png") + self.setup_bookmark(favicon_file="https_example_com.png") tasks.schedule_bookmarks_without_favicons(user) self.run_pending_task(tasks._schedule_bookmarks_without_favicons_task) @@ -403,11 +518,17 @@ class BookmarkTasksTestCase(TestCase, BookmarkFactoryMixin): self.assertEqual(task_list.count(), 3) for task in task_list: - self.assertEqual(task.task_name, 'bookmarks.services.tasks._load_favicon_task') + self.assertEqual( + task.task_name, "bookmarks.services.tasks._load_favicon_task" + ) - def test_schedule_bookmarks_without_favicons_should_only_update_user_owned_bookmarks(self): + def test_schedule_bookmarks_without_favicons_should_only_update_user_owned_bookmarks( + self, + ): user = self.get_or_create_test_user() - other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123') + other_user = User.objects.create_user( + "otheruser", "otheruser@example.com", "password123" + ) self.setup_bookmark() self.setup_bookmark() self.setup_bookmark() @@ -422,13 +543,17 @@ class BookmarkTasksTestCase(TestCase, BookmarkFactoryMixin): self.assertEqual(task_list.count(), 3) @override_settings(LD_DISABLE_BACKGROUND_TASKS=True) - def test_schedule_bookmarks_without_favicons_should_not_run_when_background_tasks_are_disabled(self): + def test_schedule_bookmarks_without_favicons_should_not_run_when_background_tasks_are_disabled( + self, + ): bookmark = self.setup_bookmark() tasks.schedule_bookmarks_without_favicons(self.get_or_create_test_user()) self.assertEqual(Task.objects.count(), 0) - def test_schedule_bookmarks_without_favicons_should_not_run_when_favicon_feature_is_disabled(self): + def test_schedule_bookmarks_without_favicons_should_not_run_when_favicon_feature_is_disabled( + self, + ): self.user.profile.enable_favicons = False self.user.profile.save() @@ -442,9 +567,9 @@ class BookmarkTasksTestCase(TestCase, BookmarkFactoryMixin): self.setup_bookmark() self.setup_bookmark() self.setup_bookmark() - self.setup_bookmark(favicon_file='https_example_com.png') - self.setup_bookmark(favicon_file='https_example_com.png') - self.setup_bookmark(favicon_file='https_example_com.png') + self.setup_bookmark(favicon_file="https_example_com.png") + self.setup_bookmark(favicon_file="https_example_com.png") + self.setup_bookmark(favicon_file="https_example_com.png") tasks.schedule_refresh_favicons(user) self.run_pending_task(tasks._schedule_refresh_favicons_task) @@ -453,11 +578,15 @@ class BookmarkTasksTestCase(TestCase, BookmarkFactoryMixin): self.assertEqual(task_list.count(), 6) for task in task_list: - self.assertEqual(task.task_name, 'bookmarks.services.tasks._load_favicon_task') + self.assertEqual( + task.task_name, "bookmarks.services.tasks._load_favicon_task" + ) def test_schedule_refresh_favicons_should_only_update_user_owned_bookmarks(self): user = self.get_or_create_test_user() - other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123') + other_user = User.objects.create_user( + "otheruser", "otheruser@example.com", "password123" + ) self.setup_bookmark() self.setup_bookmark() self.setup_bookmark() @@ -472,7 +601,9 @@ class BookmarkTasksTestCase(TestCase, BookmarkFactoryMixin): self.assertEqual(task_list.count(), 3) @override_settings(LD_DISABLE_BACKGROUND_TASKS=True) - def test_schedule_refresh_favicons_should_not_run_when_background_tasks_are_disabled(self): + def test_schedule_refresh_favicons_should_not_run_when_background_tasks_are_disabled( + self, + ): self.setup_bookmark() tasks.schedule_refresh_favicons(self.get_or_create_test_user()) @@ -485,7 +616,9 @@ class BookmarkTasksTestCase(TestCase, BookmarkFactoryMixin): self.assertEqual(Task.objects.count(), 0) - def test_schedule_refresh_favicons_should_not_run_when_favicon_feature_is_disabled(self): + def test_schedule_refresh_favicons_should_not_run_when_favicon_feature_is_disabled( + self, + ): self.user.profile.enable_favicons = False self.user.profile.save() diff --git a/bookmarks/tests/test_context_path.py b/bookmarks/tests/test_context_path.py index 98c0c45..29b1e7e 100644 --- a/bookmarks/tests/test_context_path.py +++ b/bookmarks/tests/test_context_path.py @@ -12,42 +12,45 @@ class MockUrlConf: class ContextPathTestCase(TestCase): def setUp(self): - self.siteroot_urls = importlib.import_module('siteroot.urls') + self.siteroot_urls = importlib.import_module("siteroot.urls") @override_settings(LD_CONTEXT_PATH=None) def tearDown(self): importlib.reload(self.siteroot_urls) - @override_settings(LD_CONTEXT_PATH='linkding/') + @override_settings(LD_CONTEXT_PATH="linkding/") def test_route_with_context_path(self): module = importlib.reload(self.siteroot_urls) # pass mock config instead of actual module to prevent caching the # url config in django.urls.reverse urlconf = MockUrlConf(module) test_cases = [ - ('bookmarks:index', '/linkding/bookmarks'), - ('bookmarks:bookmark-list', '/linkding/api/bookmarks/'), - ('login', '/linkding/login/'), - ('admin:bookmarks_bookmark_changelist', '/linkding/admin/bookmarks/bookmark/'), + ("bookmarks:index", "/linkding/bookmarks"), + ("bookmarks:bookmark-list", "/linkding/api/bookmarks/"), + ("login", "/linkding/login/"), + ( + "admin:bookmarks_bookmark_changelist", + "/linkding/admin/bookmarks/bookmark/", + ), ] for url_name, expected_url in test_cases: url = reverse(url_name, urlconf=urlconf) self.assertEqual(expected_url, url) - @override_settings(LD_CONTEXT_PATH='') + @override_settings(LD_CONTEXT_PATH="") def test_route_without_context_path(self): module = importlib.reload(self.siteroot_urls) # pass mock config instead of actual module to prevent caching the # url config in django.urls.reverse urlconf = MockUrlConf(module) test_cases = [ - ('bookmarks:index', '/bookmarks'), - ('bookmarks:bookmark-list', '/api/bookmarks/'), - ('login', '/login/'), - ('admin:bookmarks_bookmark_changelist', '/admin/bookmarks/bookmark/'), + ("bookmarks:index", "/bookmarks"), + ("bookmarks:bookmark-list", "/api/bookmarks/"), + ("login", "/login/"), + ("admin:bookmarks_bookmark_changelist", "/admin/bookmarks/bookmark/"), ] for url_name, expected_url in test_cases: url = reverse(url_name, urlconf=urlconf) - self.assertEqual(expected_url, url) \ No newline at end of file + self.assertEqual(expected_url, url) diff --git a/bookmarks/tests/test_create_initial_superuser_command.py b/bookmarks/tests/test_create_initial_superuser_command.py index 81caa3a..3dfb137 100644 --- a/bookmarks/tests/test_create_initial_superuser_command.py +++ b/bookmarks/tests/test_create_initial_superuser_command.py @@ -9,25 +9,28 @@ from bookmarks.management.commands.create_initial_superuser import Command class TestCreateInitialSuperuserCommand(TestCase): - @mock.patch.dict(os.environ, {'LD_SUPERUSER_NAME': 'john', 'LD_SUPERUSER_PASSWORD': 'password123'}) + @mock.patch.dict( + os.environ, + {"LD_SUPERUSER_NAME": "john", "LD_SUPERUSER_PASSWORD": "password123"}, + ) def test_create_with_password(self): Command().handle() self.assertEqual(1, User.objects.count()) user = User.objects.first() - self.assertEqual('john', user.username) + self.assertEqual("john", user.username) self.assertTrue(user.has_usable_password()) - self.assertTrue(user.check_password('password123')) + self.assertTrue(user.check_password("password123")) - @mock.patch.dict(os.environ, {'LD_SUPERUSER_NAME': 'john'}) + @mock.patch.dict(os.environ, {"LD_SUPERUSER_NAME": "john"}) def test_create_without_password(self): Command().handle() self.assertEqual(1, User.objects.count()) user = User.objects.first() - self.assertEqual('john', user.username) + self.assertEqual("john", user.username) self.assertFalse(user.has_usable_password()) def test_create_without_options(self): @@ -35,11 +38,13 @@ class TestCreateInitialSuperuserCommand(TestCase): self.assertEqual(0, User.objects.count()) - @mock.patch.dict(os.environ, {'LD_SUPERUSER_NAME': 'john', 'LD_SUPERUSER_PASSWORD': 'password123'}) + @mock.patch.dict( + os.environ, + {"LD_SUPERUSER_NAME": "john", "LD_SUPERUSER_PASSWORD": "password123"}, + ) def test_create_multiple_times(self): Command().handle() Command().handle() Command().handle() self.assertEqual(1, User.objects.count()) - diff --git a/bookmarks/tests/test_exporter.py b/bookmarks/tests/test_exporter.py index 033246c..4045201 100644 --- a/bookmarks/tests/test_exporter.py +++ b/bookmarks/tests/test_exporter.py @@ -11,60 +11,93 @@ class ExporterTestCase(TestCase, BookmarkFactoryMixin): timestamp = int(added.timestamp()) bookmarks = [ - self.setup_bookmark(url='https://example.com/1', title='Title 1', added=added, - description='Example description'), - self.setup_bookmark(url='https://example.com/2', title='Title 2', added=added, - tags=[self.setup_tag(name='tag1'), self.setup_tag(name='tag2'), - self.setup_tag(name='tag3')]), - self.setup_bookmark(url='https://example.com/3', title='Title 3', added=added, unread=True), - self.setup_bookmark(url='https://example.com/4', title='Title 4', added=added, shared=True), - self.setup_bookmark(url='https://example.com/5', title='Title 5', added=added, shared=True, - description='Example description', notes='Example notes'), - self.setup_bookmark(url='https://example.com/6', title='Title 6', added=added, shared=True, - notes='Example notes'), - self.setup_bookmark(url='https://example.com/7', title='Title 7', added=added, is_archived=True), - self.setup_bookmark(url='https://example.com/8', title='Title 8', added=added, - tags=[self.setup_tag(name='tag4'), self.setup_tag(name='tag5')], is_archived=True), + self.setup_bookmark( + url="https://example.com/1", + title="Title 1", + added=added, + description="Example description", + ), + self.setup_bookmark( + url="https://example.com/2", + title="Title 2", + added=added, + tags=[ + self.setup_tag(name="tag1"), + self.setup_tag(name="tag2"), + self.setup_tag(name="tag3"), + ], + ), + self.setup_bookmark( + url="https://example.com/3", title="Title 3", added=added, unread=True + ), + self.setup_bookmark( + url="https://example.com/4", title="Title 4", added=added, shared=True + ), + self.setup_bookmark( + url="https://example.com/5", + title="Title 5", + added=added, + shared=True, + description="Example description", + notes="Example notes", + ), + self.setup_bookmark( + url="https://example.com/6", + title="Title 6", + added=added, + shared=True, + notes="Example notes", + ), + self.setup_bookmark( + url="https://example.com/7", + title="Title 7", + added=added, + is_archived=True, + ), + self.setup_bookmark( + url="https://example.com/8", + title="Title 8", + added=added, + tags=[self.setup_tag(name="tag4"), self.setup_tag(name="tag5")], + is_archived=True, + ), ] html = exporter.export_netscape_html(bookmarks) lines = [ f'
            Title 1', - '
            Example description', + "
            Example description", f'
            Title 2', f'
            Title 3', f'
            Title 4', f'
            Title 5', - '
            Example description[linkding-notes]Example notes[/linkding-notes]', + "
            Example description[linkding-notes]Example notes[/linkding-notes]", f'
            Title 6', - '
            [linkding-notes]Example notes[/linkding-notes]', + "
            [linkding-notes]Example notes[/linkding-notes]", f'
            Title 7', f'
            Title 8', ] - self.assertIn('\n\r'.join(lines), html) + self.assertIn("\n\r".join(lines), html) def test_escape_html(self): bookmark = self.setup_bookmark( - title='