mirror of
https://github.com/sissbruecker/linkding
synced 2024-11-22 03:13:02 +00:00
Add black code formatter
This commit is contained in:
parent
6775633be5
commit
98b9a9c1a0
128 changed files with 7181 additions and 4264 deletions
15
Makefile
Normal file
15
Makefile
Normal file
|
@ -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
|
19
README.md
19
README.md
|
@ -281,6 +281,20 @@ python3 manage.py runserver
|
||||||
```
|
```
|
||||||
The frontend is now available under http://localhost:8000
|
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
|
### 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)
|
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
|
python3 manage.py runserver
|
||||||
```
|
```
|
||||||
The frontend is now available under http://localhost:8000
|
The frontend is now available under http://localhost:8000
|
||||||
|
|
||||||
Run all tests with pytest
|
|
||||||
```
|
|
||||||
pytest
|
|
||||||
```
|
|
||||||
|
|
|
@ -14,80 +14,123 @@ from bookmarks.services.bookmarks import archive_bookmark, unarchive_bookmark
|
||||||
|
|
||||||
|
|
||||||
class LinkdingAdminSite(AdminSite):
|
class LinkdingAdminSite(AdminSite):
|
||||||
site_header = 'linkding administration'
|
site_header = "linkding administration"
|
||||||
site_title = 'linkding Admin'
|
site_title = "linkding Admin"
|
||||||
|
|
||||||
|
|
||||||
class AdminBookmark(admin.ModelAdmin):
|
class AdminBookmark(admin.ModelAdmin):
|
||||||
list_display = ('resolved_title', 'url', 'is_archived', 'owner', 'date_added')
|
list_display = ("resolved_title", "url", "is_archived", "owner", "date_added")
|
||||||
search_fields = ('title', 'description', 'website_title', 'website_description', 'url', 'tags__name')
|
search_fields = (
|
||||||
list_filter = ('owner__username', 'is_archived', 'unread', 'tags',)
|
"title",
|
||||||
ordering = ('-date_added',)
|
"description",
|
||||||
actions = ['delete_selected_bookmarks', 'archive_selected_bookmarks', 'unarchive_selected_bookmarks', 'mark_as_read', 'mark_as_unread']
|
"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):
|
def get_actions(self, request):
|
||||||
actions = super().get_actions(request)
|
actions = super().get_actions(request)
|
||||||
# Remove default delete action, which gets replaced by delete_selected_bookmarks below
|
# 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
|
# 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)
|
# 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
|
return actions
|
||||||
|
|
||||||
def delete_selected_bookmarks(self, request, queryset: QuerySet):
|
def delete_selected_bookmarks(self, request, queryset: QuerySet):
|
||||||
bookmarks_count = queryset.count()
|
bookmarks_count = queryset.count()
|
||||||
for bookmark in queryset:
|
for bookmark in queryset:
|
||||||
bookmark.delete()
|
bookmark.delete()
|
||||||
self.message_user(request, ngettext(
|
self.message_user(
|
||||||
'%d bookmark was successfully deleted.',
|
request,
|
||||||
'%d bookmarks were successfully deleted.',
|
ngettext(
|
||||||
|
"%d bookmark was successfully deleted.",
|
||||||
|
"%d bookmarks were successfully deleted.",
|
||||||
bookmarks_count,
|
bookmarks_count,
|
||||||
) % bookmarks_count, messages.SUCCESS)
|
)
|
||||||
|
% bookmarks_count,
|
||||||
|
messages.SUCCESS,
|
||||||
|
)
|
||||||
|
|
||||||
def archive_selected_bookmarks(self, request, queryset: QuerySet):
|
def archive_selected_bookmarks(self, request, queryset: QuerySet):
|
||||||
for bookmark in queryset:
|
for bookmark in queryset:
|
||||||
archive_bookmark(bookmark)
|
archive_bookmark(bookmark)
|
||||||
bookmarks_count = queryset.count()
|
bookmarks_count = queryset.count()
|
||||||
self.message_user(request, ngettext(
|
self.message_user(
|
||||||
'%d bookmark was successfully archived.',
|
request,
|
||||||
'%d bookmarks were successfully archived.',
|
ngettext(
|
||||||
|
"%d bookmark was successfully archived.",
|
||||||
|
"%d bookmarks were successfully archived.",
|
||||||
bookmarks_count,
|
bookmarks_count,
|
||||||
) % bookmarks_count, messages.SUCCESS)
|
)
|
||||||
|
% bookmarks_count,
|
||||||
|
messages.SUCCESS,
|
||||||
|
)
|
||||||
|
|
||||||
def unarchive_selected_bookmarks(self, request, queryset: QuerySet):
|
def unarchive_selected_bookmarks(self, request, queryset: QuerySet):
|
||||||
for bookmark in queryset:
|
for bookmark in queryset:
|
||||||
unarchive_bookmark(bookmark)
|
unarchive_bookmark(bookmark)
|
||||||
bookmarks_count = queryset.count()
|
bookmarks_count = queryset.count()
|
||||||
self.message_user(request, ngettext(
|
self.message_user(
|
||||||
'%d bookmark was successfully unarchived.',
|
request,
|
||||||
'%d bookmarks were successfully unarchived.',
|
ngettext(
|
||||||
|
"%d bookmark was successfully unarchived.",
|
||||||
|
"%d bookmarks were successfully unarchived.",
|
||||||
bookmarks_count,
|
bookmarks_count,
|
||||||
) % bookmarks_count, messages.SUCCESS)
|
)
|
||||||
|
% bookmarks_count,
|
||||||
|
messages.SUCCESS,
|
||||||
|
)
|
||||||
|
|
||||||
def mark_as_read(self, request, queryset: QuerySet):
|
def mark_as_read(self, request, queryset: QuerySet):
|
||||||
bookmarks_count = queryset.count()
|
bookmarks_count = queryset.count()
|
||||||
queryset.update(unread=False)
|
queryset.update(unread=False)
|
||||||
self.message_user(request, ngettext(
|
self.message_user(
|
||||||
'%d bookmark marked as read.',
|
request,
|
||||||
'%d bookmarks marked as read.',
|
ngettext(
|
||||||
|
"%d bookmark marked as read.",
|
||||||
|
"%d bookmarks marked as read.",
|
||||||
bookmarks_count,
|
bookmarks_count,
|
||||||
) % bookmarks_count, messages.SUCCESS)
|
)
|
||||||
|
% bookmarks_count,
|
||||||
|
messages.SUCCESS,
|
||||||
|
)
|
||||||
|
|
||||||
def mark_as_unread(self, request, queryset: QuerySet):
|
def mark_as_unread(self, request, queryset: QuerySet):
|
||||||
bookmarks_count = queryset.count()
|
bookmarks_count = queryset.count()
|
||||||
queryset.update(unread=True)
|
queryset.update(unread=True)
|
||||||
self.message_user(request, ngettext(
|
self.message_user(
|
||||||
'%d bookmark marked as unread.',
|
request,
|
||||||
'%d bookmarks marked as unread.',
|
ngettext(
|
||||||
|
"%d bookmark marked as unread.",
|
||||||
|
"%d bookmarks marked as unread.",
|
||||||
bookmarks_count,
|
bookmarks_count,
|
||||||
) % bookmarks_count, messages.SUCCESS)
|
)
|
||||||
|
% bookmarks_count,
|
||||||
|
messages.SUCCESS,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class AdminTag(admin.ModelAdmin):
|
class AdminTag(admin.ModelAdmin):
|
||||||
list_display = ('name', 'bookmarks_count', 'owner', 'date_added')
|
list_display = ("name", "bookmarks_count", "owner", "date_added")
|
||||||
search_fields = ('name', 'owner__username')
|
search_fields = ("name", "owner__username")
|
||||||
list_filter = ('owner__username',)
|
list_filter = ("owner__username",)
|
||||||
ordering = ('-date_added',)
|
ordering = ("-date_added",)
|
||||||
actions = ['delete_unused_tags']
|
actions = ["delete_unused_tags"]
|
||||||
|
|
||||||
def get_queryset(self, request):
|
def get_queryset(self, request):
|
||||||
queryset = super().get_queryset(request)
|
queryset = super().get_queryset(request)
|
||||||
|
@ -97,7 +140,7 @@ class AdminTag(admin.ModelAdmin):
|
||||||
def bookmarks_count(self, obj):
|
def bookmarks_count(self, obj):
|
||||||
return obj.bookmarks_count
|
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):
|
def delete_unused_tags(self, request, queryset: QuerySet):
|
||||||
unused_tags = queryset.filter(bookmark__isnull=True)
|
unused_tags = queryset.filter(bookmark__isnull=True)
|
||||||
|
@ -106,23 +149,33 @@ class AdminTag(admin.ModelAdmin):
|
||||||
tag.delete()
|
tag.delete()
|
||||||
|
|
||||||
if unused_tags_count > 0:
|
if unused_tags_count > 0:
|
||||||
self.message_user(request, ngettext(
|
self.message_user(
|
||||||
'%d unused tag was successfully deleted.',
|
request,
|
||||||
'%d unused tags were successfully deleted.',
|
ngettext(
|
||||||
|
"%d unused tag was successfully deleted.",
|
||||||
|
"%d unused tags were successfully deleted.",
|
||||||
unused_tags_count,
|
unused_tags_count,
|
||||||
) % unused_tags_count, messages.SUCCESS)
|
)
|
||||||
|
% unused_tags_count,
|
||||||
|
messages.SUCCESS,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.message_user(request, gettext(
|
self.message_user(
|
||||||
'There were no unused tags in the selection',
|
request,
|
||||||
), messages.SUCCESS)
|
gettext(
|
||||||
|
"There were no unused tags in the selection",
|
||||||
|
),
|
||||||
|
messages.SUCCESS,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class AdminUserProfileInline(admin.StackedInline):
|
class AdminUserProfileInline(admin.StackedInline):
|
||||||
model = UserProfile
|
model = UserProfile
|
||||||
can_delete = False
|
can_delete = False
|
||||||
verbose_name_plural = 'Profile'
|
verbose_name_plural = "Profile"
|
||||||
fk_name = 'user'
|
fk_name = "user"
|
||||||
readonly_fields = ('search_preferences', )
|
readonly_fields = ("search_preferences",)
|
||||||
|
|
||||||
|
|
||||||
class AdminCustomUser(UserAdmin):
|
class AdminCustomUser(UserAdmin):
|
||||||
inlines = (AdminUserProfileInline,)
|
inlines = (AdminUserProfileInline,)
|
||||||
|
@ -134,15 +187,15 @@ class AdminCustomUser(UserAdmin):
|
||||||
|
|
||||||
|
|
||||||
class AdminToast(admin.ModelAdmin):
|
class AdminToast(admin.ModelAdmin):
|
||||||
list_display = ('key', 'message', 'owner', 'acknowledged')
|
list_display = ("key", "message", "owner", "acknowledged")
|
||||||
search_fields = ('key', 'message')
|
search_fields = ("key", "message")
|
||||||
list_filter = ('owner__username',)
|
list_filter = ("owner__username",)
|
||||||
|
|
||||||
|
|
||||||
class AdminFeedToken(admin.ModelAdmin):
|
class AdminFeedToken(admin.ModelAdmin):
|
||||||
list_display = ('key', 'user')
|
list_display = ("key", "user")
|
||||||
search_fields = ['key']
|
search_fields = ["key"]
|
||||||
list_filter = ('user__username',)
|
list_filter = ("user__username",)
|
||||||
|
|
||||||
|
|
||||||
linkding_admin_site = LinkdingAdminSite()
|
linkding_admin_site = LinkdingAdminSite()
|
||||||
|
|
|
@ -5,18 +5,28 @@ from rest_framework.response import Response
|
||||||
from rest_framework.routers import DefaultRouter
|
from rest_framework.routers import DefaultRouter
|
||||||
|
|
||||||
from bookmarks import queries
|
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.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
|
from bookmarks.services.website_loader import WebsiteMetadata
|
||||||
|
|
||||||
|
|
||||||
class BookmarkViewSet(viewsets.GenericViewSet,
|
class BookmarkViewSet(
|
||||||
|
viewsets.GenericViewSet,
|
||||||
mixins.ListModelMixin,
|
mixins.ListModelMixin,
|
||||||
mixins.RetrieveModelMixin,
|
mixins.RetrieveModelMixin,
|
||||||
mixins.CreateModelMixin,
|
mixins.CreateModelMixin,
|
||||||
mixins.UpdateModelMixin,
|
mixins.UpdateModelMixin,
|
||||||
mixins.DestroyModelMixin):
|
mixins.DestroyModelMixin,
|
||||||
|
):
|
||||||
serializer_class = BookmarkSerializer
|
serializer_class = BookmarkSerializer
|
||||||
|
|
||||||
def get_permissions(self):
|
def get_permissions(self):
|
||||||
|
@ -24,7 +34,7 @@ class BookmarkViewSet(viewsets.GenericViewSet,
|
||||||
# The shared action should still filter bookmarks so that
|
# The shared action should still filter bookmarks so that
|
||||||
# unauthenticated users only see bookmarks from users that have public
|
# unauthenticated users only see bookmarks from users that have public
|
||||||
# sharing explicitly enabled
|
# sharing explicitly enabled
|
||||||
if self.action == 'shared':
|
if self.action == "shared":
|
||||||
return [AllowAny()]
|
return [AllowAny()]
|
||||||
|
|
||||||
# Otherwise use default permissions which should require authentication
|
# Otherwise use default permissions which should require authentication
|
||||||
|
@ -33,7 +43,7 @@ class BookmarkViewSet(viewsets.GenericViewSet,
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
user = self.request.user
|
user = self.request.user
|
||||||
# For list action, use query set that applies search and tag projections
|
# 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)
|
search = BookmarkSearch.from_request(self.request.GET)
|
||||||
return queries.query_bookmarks(user, user.profile, search)
|
return queries.query_bookmarks(user, user.profile, search)
|
||||||
|
|
||||||
|
@ -41,9 +51,9 @@ class BookmarkViewSet(viewsets.GenericViewSet,
|
||||||
return Bookmark.objects.all().filter(owner=user)
|
return Bookmark.objects.all().filter(owner=user)
|
||||||
|
|
||||||
def get_serializer_context(self):
|
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):
|
def archived(self, request):
|
||||||
user = request.user
|
user = request.user
|
||||||
search = BookmarkSearch.from_request(request.GET)
|
search = BookmarkSearch.from_request(request.GET)
|
||||||
|
@ -53,51 +63,59 @@ class BookmarkViewSet(viewsets.GenericViewSet,
|
||||||
data = serializer(page, many=True).data
|
data = serializer(page, many=True).data
|
||||||
return self.get_paginated_response(data)
|
return self.get_paginated_response(data)
|
||||||
|
|
||||||
@action(methods=['get'], detail=False)
|
@action(methods=["get"], detail=False)
|
||||||
def shared(self, request):
|
def shared(self, request):
|
||||||
search = BookmarkSearch.from_request(request.GET)
|
search = BookmarkSearch.from_request(request.GET)
|
||||||
user = User.objects.filter(username=search.user).first()
|
user = User.objects.filter(username=search.user).first()
|
||||||
public_only = not request.user.is_authenticated
|
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)
|
page = self.paginate_queryset(query_set)
|
||||||
serializer = self.get_serializer_class()
|
serializer = self.get_serializer_class()
|
||||||
data = serializer(page, many=True).data
|
data = serializer(page, many=True).data
|
||||||
return self.get_paginated_response(data)
|
return self.get_paginated_response(data)
|
||||||
|
|
||||||
@action(methods=['post'], detail=True)
|
@action(methods=["post"], detail=True)
|
||||||
def archive(self, request, pk):
|
def archive(self, request, pk):
|
||||||
bookmark = self.get_object()
|
bookmark = self.get_object()
|
||||||
archive_bookmark(bookmark)
|
archive_bookmark(bookmark)
|
||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
@action(methods=['post'], detail=True)
|
@action(methods=["post"], detail=True)
|
||||||
def unarchive(self, request, pk):
|
def unarchive(self, request, pk):
|
||||||
bookmark = self.get_object()
|
bookmark = self.get_object()
|
||||||
unarchive_bookmark(bookmark)
|
unarchive_bookmark(bookmark)
|
||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
@action(methods=['get'], detail=False)
|
@action(methods=["get"], detail=False)
|
||||||
def check(self, request):
|
def check(self, request):
|
||||||
url = request.GET.get('url')
|
url = request.GET.get("url")
|
||||||
bookmark = Bookmark.objects.filter(owner=request.user, url=url).first()
|
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
|
# Either return metadata from existing bookmark, or scrape from URL
|
||||||
if bookmark:
|
if bookmark:
|
||||||
metadata = WebsiteMetadata(url, bookmark.website_title, bookmark.website_description)
|
metadata = WebsiteMetadata(
|
||||||
|
url, bookmark.website_title, bookmark.website_description
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
metadata = website_loader.load_website_metadata(url)
|
metadata = website_loader.load_website_metadata(url)
|
||||||
|
|
||||||
return Response({
|
return Response(
|
||||||
'bookmark': existing_bookmark_data,
|
{"bookmark": existing_bookmark_data, "metadata": metadata.to_dict()},
|
||||||
'metadata': metadata.to_dict()
|
status=status.HTTP_200_OK,
|
||||||
}, status=status.HTTP_200_OK)
|
)
|
||||||
|
|
||||||
|
|
||||||
class TagViewSet(viewsets.GenericViewSet,
|
class TagViewSet(
|
||||||
|
viewsets.GenericViewSet,
|
||||||
mixins.ListModelMixin,
|
mixins.ListModelMixin,
|
||||||
mixins.RetrieveModelMixin,
|
mixins.RetrieveModelMixin,
|
||||||
mixins.CreateModelMixin):
|
mixins.CreateModelMixin,
|
||||||
|
):
|
||||||
serializer_class = TagSerializer
|
serializer_class = TagSerializer
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
@ -105,16 +123,16 @@ class TagViewSet(viewsets.GenericViewSet,
|
||||||
return Tag.objects.all().filter(owner=user)
|
return Tag.objects.all().filter(owner=user)
|
||||||
|
|
||||||
def get_serializer_context(self):
|
def get_serializer_context(self):
|
||||||
return {'user': self.request.user}
|
return {"user": self.request.user}
|
||||||
|
|
||||||
|
|
||||||
class UserViewSet(viewsets.GenericViewSet):
|
class UserViewSet(viewsets.GenericViewSet):
|
||||||
@action(methods=['get'], detail=False)
|
@action(methods=["get"], detail=False)
|
||||||
def profile(self, request):
|
def profile(self, request):
|
||||||
return Response(UserProfileSerializer(request.user.profile).data)
|
return Response(UserProfileSerializer(request.user.profile).data)
|
||||||
|
|
||||||
|
|
||||||
router = DefaultRouter()
|
router = DefaultRouter()
|
||||||
router.register(r'bookmarks', BookmarkViewSet, basename='bookmark')
|
router.register(r"bookmarks", BookmarkViewSet, basename="bookmark")
|
||||||
router.register(r'tags', TagViewSet, basename='tag')
|
router.register(r"tags", TagViewSet, basename="tag")
|
||||||
router.register(r'user', UserViewSet, basename='user')
|
router.register(r"user", UserViewSet, basename="user")
|
||||||
|
|
|
@ -14,7 +14,7 @@ class TagListField(serializers.ListField):
|
||||||
class BookmarkListSerializer(ListSerializer):
|
class BookmarkListSerializer(ListSerializer):
|
||||||
def to_representation(self, data):
|
def to_representation(self, data):
|
||||||
# Prefetch nested relations to avoid n+1 queries
|
# Prefetch nested relations to avoid n+1 queries
|
||||||
prefetch_related_objects(data, 'tags')
|
prefetch_related_objects(data, "tags")
|
||||||
|
|
||||||
return super().to_representation(data)
|
return super().to_representation(data)
|
||||||
|
|
||||||
|
@ -23,32 +23,32 @@ class BookmarkSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Bookmark
|
model = Bookmark
|
||||||
fields = [
|
fields = [
|
||||||
'id',
|
"id",
|
||||||
'url',
|
"url",
|
||||||
'title',
|
"title",
|
||||||
'description',
|
"description",
|
||||||
'notes',
|
"notes",
|
||||||
'website_title',
|
"website_title",
|
||||||
'website_description',
|
"website_description",
|
||||||
'is_archived',
|
"is_archived",
|
||||||
'unread',
|
"unread",
|
||||||
'shared',
|
"shared",
|
||||||
'tag_names',
|
"tag_names",
|
||||||
'date_added',
|
"date_added",
|
||||||
'date_modified'
|
"date_modified",
|
||||||
]
|
]
|
||||||
read_only_fields = [
|
read_only_fields = [
|
||||||
'website_title',
|
"website_title",
|
||||||
'website_description',
|
"website_description",
|
||||||
'date_added',
|
"date_added",
|
||||||
'date_modified'
|
"date_modified",
|
||||||
]
|
]
|
||||||
list_serializer_class = BookmarkListSerializer
|
list_serializer_class = BookmarkListSerializer
|
||||||
|
|
||||||
# Override optional char fields to provide default value
|
# Override optional char fields to provide default value
|
||||||
title = 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='')
|
description = serializers.CharField(required=False, allow_blank=True, default="")
|
||||||
notes = 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)
|
is_archived = serializers.BooleanField(required=False, default=False)
|
||||||
unread = serializers.BooleanField(required=False, default=False)
|
unread = serializers.BooleanField(required=False, default=False)
|
||||||
shared = 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):
|
def create(self, validated_data):
|
||||||
bookmark = Bookmark()
|
bookmark = Bookmark()
|
||||||
bookmark.url = validated_data['url']
|
bookmark.url = validated_data["url"]
|
||||||
bookmark.title = validated_data['title']
|
bookmark.title = validated_data["title"]
|
||||||
bookmark.description = validated_data['description']
|
bookmark.description = validated_data["description"]
|
||||||
bookmark.notes = validated_data['notes']
|
bookmark.notes = validated_data["notes"]
|
||||||
bookmark.is_archived = validated_data['is_archived']
|
bookmark.is_archived = validated_data["is_archived"]
|
||||||
bookmark.unread = validated_data['unread']
|
bookmark.unread = validated_data["unread"]
|
||||||
bookmark.shared = validated_data['shared']
|
bookmark.shared = validated_data["shared"]
|
||||||
tag_string = build_tag_string(validated_data['tag_names'])
|
tag_string = build_tag_string(validated_data["tag_names"])
|
||||||
return create_bookmark(bookmark, tag_string, self.context['user'])
|
return create_bookmark(bookmark, tag_string, self.context["user"])
|
||||||
|
|
||||||
def update(self, instance: Bookmark, validated_data):
|
def update(self, instance: Bookmark, validated_data):
|
||||||
# Update fields if they were provided in the payload
|
# 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:
|
if key in validated_data:
|
||||||
setattr(instance, key, validated_data[key])
|
setattr(instance, key, validated_data[key])
|
||||||
|
|
||||||
# Use tag string from payload, or use bookmark's current tags as fallback
|
# Use tag string from payload, or use bookmark's current tags as fallback
|
||||||
tag_string = build_tag_string(instance.tag_names)
|
tag_string = build_tag_string(instance.tag_names)
|
||||||
if 'tag_names' in validated_data:
|
if "tag_names" in validated_data:
|
||||||
tag_string = build_tag_string(validated_data['tag_names'])
|
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 TagSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Tag
|
model = Tag
|
||||||
fields = ['id', 'name', 'date_added']
|
fields = ["id", "name", "date_added"]
|
||||||
read_only_fields = ['date_added']
|
read_only_fields = ["date_added"]
|
||||||
|
|
||||||
def create(self, validated_data):
|
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):
|
class UserProfileSerializer(serializers.ModelSerializer):
|
||||||
|
|
|
@ -2,7 +2,7 @@ from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
class BookmarksConfig(AppConfig):
|
class BookmarksConfig(AppConfig):
|
||||||
name = 'bookmarks'
|
name = "bookmarks"
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
# Register signal handlers
|
# Register signal handlers
|
||||||
|
|
|
@ -5,28 +5,32 @@ from bookmarks import utils
|
||||||
|
|
||||||
def toasts(request):
|
def toasts(request):
|
||||||
user = request.user
|
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
|
has_toasts = len(toast_messages) > 0
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'has_toasts': has_toasts,
|
"has_toasts": has_toasts,
|
||||||
'toast_messages': toast_messages,
|
"toast_messages": toast_messages,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def public_shares(request):
|
def public_shares(request):
|
||||||
# Only check for public shares for anonymous users
|
# Only check for public shares for anonymous users
|
||||||
if not request.user.is_authenticated:
|
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
|
has_public_shares = query_set.count() > 0
|
||||||
return {
|
return {
|
||||||
'has_public_shares': has_public_shares,
|
"has_public_shares": has_public_shares,
|
||||||
}
|
}
|
||||||
|
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
def app_version(request):
|
def app_version(request):
|
||||||
return {
|
return {"app_version": utils.app_version}
|
||||||
'app_version': utils.app_version
|
|
||||||
}
|
|
||||||
|
|
|
@ -6,38 +6,54 @@ from bookmarks.e2e.helpers import LinkdingE2ETestCase
|
||||||
|
|
||||||
class BookmarkFormE2ETestCase(LinkdingE2ETestCase):
|
class BookmarkFormE2ETestCase(LinkdingE2ETestCase):
|
||||||
def test_create_should_check_for_existing_bookmark(self):
|
def test_create_should_check_for_existing_bookmark(self):
|
||||||
existing_bookmark = self.setup_bookmark(title='Existing title',
|
existing_bookmark = self.setup_bookmark(
|
||||||
description='Existing description',
|
title="Existing title",
|
||||||
notes='Existing notes',
|
description="Existing description",
|
||||||
tags=[self.setup_tag(name='tag1'), self.setup_tag(name='tag2')],
|
notes="Existing notes",
|
||||||
website_title='Existing website title',
|
tags=[self.setup_tag(name="tag1"), self.setup_tag(name="tag2")],
|
||||||
website_description='Existing website description',
|
website_title="Existing website title",
|
||||||
unread=True)
|
website_description="Existing website description",
|
||||||
tag_names = ' '.join(existing_bookmark.tag_names)
|
unread=True,
|
||||||
|
)
|
||||||
|
tag_names = " ".join(existing_bookmark.tag_names)
|
||||||
|
|
||||||
with sync_playwright() as p:
|
with sync_playwright() as p:
|
||||||
browser = self.setup_browser(p)
|
browser = self.setup_browser(p)
|
||||||
page = browser.new_page()
|
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
|
# 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
|
# 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
|
# Form should be pre-filled with data from existing bookmark
|
||||||
self.assertEqual(existing_bookmark.title, page.get_by_label('Title').input_value())
|
self.assertEqual(
|
||||||
self.assertEqual(existing_bookmark.description, page.get_by_label('Description').input_value())
|
existing_bookmark.title, page.get_by_label("Title").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(
|
||||||
self.assertEqual(existing_bookmark.website_description,
|
existing_bookmark.description,
|
||||||
page.get_by_label('Description').get_attribute('placeholder'))
|
page.get_by_label("Description").input_value(),
|
||||||
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.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
|
# 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
|
# 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()
|
browser.close()
|
||||||
|
|
||||||
|
@ -47,21 +63,25 @@ class BookmarkFormE2ETestCase(LinkdingE2ETestCase):
|
||||||
with sync_playwright() as p:
|
with sync_playwright() as p:
|
||||||
browser = self.setup_browser(p)
|
browser = self.setup_browser(p)
|
||||||
page = browser.new_page()
|
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.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):
|
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:
|
with sync_playwright() as p:
|
||||||
browser = self.setup_browser(p)
|
browser = self.setup_browser(p)
|
||||||
page = browser.new_page()
|
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')
|
details = page.locator("details.notes")
|
||||||
expect(details).not_to_have_attribute('open', value='')
|
expect(details).not_to_have_attribute("open", value="")
|
||||||
|
|
||||||
page.get_by_label('URL').fill(bookmark.url)
|
page.get_by_label("URL").fill(bookmark.url)
|
||||||
expect(details).to_have_attribute('open', value='')
|
expect(details).to_have_attribute("open", value="")
|
||||||
|
|
|
@ -9,15 +9,15 @@ from bookmarks.e2e.helpers import LinkdingE2ETestCase
|
||||||
class BookmarkItemE2ETestCase(LinkdingE2ETestCase):
|
class BookmarkItemE2ETestCase(LinkdingE2ETestCase):
|
||||||
@skip("Fails in CI, needs investigation")
|
@skip("Fails in CI, needs investigation")
|
||||||
def test_toggle_notes_should_show_hide_notes(self):
|
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:
|
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()
|
expect(notes).to_be_hidden()
|
||||||
|
|
||||||
toggle_notes = page.locator('li button.toggle-notes')
|
toggle_notes = page.locator("li button.toggle-notes")
|
||||||
toggle_notes.click()
|
toggle_notes.click()
|
||||||
expect(notes).to_be_visible()
|
expect(notes).to_be_visible()
|
||||||
|
|
||||||
|
|
|
@ -9,100 +9,180 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase):
|
||||||
def setup_test_data(self):
|
def setup_test_data(self):
|
||||||
self.setup_numbered_bookmarks(50)
|
self.setup_numbered_bookmarks(50)
|
||||||
self.setup_numbered_bookmarks(50, archived=True)
|
self.setup_numbered_bookmarks(50, archived=True)
|
||||||
self.setup_numbered_bookmarks(50, prefix='foo')
|
self.setup_numbered_bookmarks(50, prefix="foo")
|
||||||
self.setup_numbered_bookmarks(50, archived=True, 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(
|
||||||
self.assertEqual(50, Bookmark.objects.filter(is_archived=True, title__startswith='Archived Bookmark').count())
|
50,
|
||||||
self.assertEqual(50, Bookmark.objects.filter(is_archived=False, title__startswith='foo').count())
|
Bookmark.objects.filter(
|
||||||
self.assertEqual(50, Bookmark.objects.filter(is_archived=True, title__startswith='foo').count())
|
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):
|
def test_active_bookmarks_bulk_select_across(self):
|
||||||
self.setup_test_data()
|
self.setup_test_data()
|
||||||
|
|
||||||
with sync_playwright() as p:
|
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_toggle().click()
|
||||||
self.locate_bulk_edit_select_all().click()
|
self.locate_bulk_edit_select_all().click()
|
||||||
self.locate_bulk_edit_select_across().click()
|
self.locate_bulk_edit_select_across().click()
|
||||||
|
|
||||||
self.select_bulk_action('Delete')
|
self.select_bulk_action("Delete")
|
||||||
self.locate_bulk_edit_bar().get_by_text('Execute').click()
|
self.locate_bulk_edit_bar().get_by_text("Execute").click()
|
||||||
self.locate_bulk_edit_bar().get_by_text('Confirm').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(
|
||||||
self.assertEqual(50, Bookmark.objects.filter(is_archived=True, title__startswith='Archived Bookmark').count())
|
0,
|
||||||
self.assertEqual(0, Bookmark.objects.filter(is_archived=False, title__startswith='foo').count())
|
Bookmark.objects.filter(
|
||||||
self.assertEqual(50, Bookmark.objects.filter(is_archived=True, title__startswith='foo').count())
|
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):
|
def test_archived_bookmarks_bulk_select_across(self):
|
||||||
self.setup_test_data()
|
self.setup_test_data()
|
||||||
|
|
||||||
with sync_playwright() as p:
|
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_toggle().click()
|
||||||
self.locate_bulk_edit_select_all().click()
|
self.locate_bulk_edit_select_all().click()
|
||||||
self.locate_bulk_edit_select_across().click()
|
self.locate_bulk_edit_select_across().click()
|
||||||
|
|
||||||
self.select_bulk_action('Delete')
|
self.select_bulk_action("Delete")
|
||||||
self.locate_bulk_edit_bar().get_by_text('Execute').click()
|
self.locate_bulk_edit_bar().get_by_text("Execute").click()
|
||||||
self.locate_bulk_edit_bar().get_by_text('Confirm').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(
|
||||||
self.assertEqual(0, Bookmark.objects.filter(is_archived=True, title__startswith='Archived Bookmark').count())
|
50,
|
||||||
self.assertEqual(50, Bookmark.objects.filter(is_archived=False, title__startswith='foo').count())
|
Bookmark.objects.filter(
|
||||||
self.assertEqual(0, Bookmark.objects.filter(is_archived=True, title__startswith='foo').count())
|
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):
|
def test_active_bookmarks_bulk_select_across_respects_query(self):
|
||||||
self.setup_test_data()
|
self.setup_test_data()
|
||||||
|
|
||||||
with sync_playwright() as p:
|
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_toggle().click()
|
||||||
self.locate_bulk_edit_select_all().click()
|
self.locate_bulk_edit_select_all().click()
|
||||||
self.locate_bulk_edit_select_across().click()
|
self.locate_bulk_edit_select_across().click()
|
||||||
|
|
||||||
self.select_bulk_action('Delete')
|
self.select_bulk_action("Delete")
|
||||||
self.locate_bulk_edit_bar().get_by_text('Execute').click()
|
self.locate_bulk_edit_bar().get_by_text("Execute").click()
|
||||||
self.locate_bulk_edit_bar().get_by_text('Confirm').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(
|
||||||
self.assertEqual(50, Bookmark.objects.filter(is_archived=True, title__startswith='Archived Bookmark').count())
|
50,
|
||||||
self.assertEqual(0, Bookmark.objects.filter(is_archived=False, title__startswith='foo').count())
|
Bookmark.objects.filter(
|
||||||
self.assertEqual(50, Bookmark.objects.filter(is_archived=True, title__startswith='foo').count())
|
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):
|
def test_archived_bookmarks_bulk_select_across_respects_query(self):
|
||||||
self.setup_test_data()
|
self.setup_test_data()
|
||||||
|
|
||||||
with sync_playwright() as p:
|
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_toggle().click()
|
||||||
self.locate_bulk_edit_select_all().click()
|
self.locate_bulk_edit_select_all().click()
|
||||||
self.locate_bulk_edit_select_across().click()
|
self.locate_bulk_edit_select_across().click()
|
||||||
|
|
||||||
self.select_bulk_action('Delete')
|
self.select_bulk_action("Delete")
|
||||||
self.locate_bulk_edit_bar().get_by_text('Execute').click()
|
self.locate_bulk_edit_bar().get_by_text("Execute").click()
|
||||||
self.locate_bulk_edit_bar().get_by_text('Confirm').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(
|
||||||
self.assertEqual(50, Bookmark.objects.filter(is_archived=True, title__startswith='Archived Bookmark').count())
|
50,
|
||||||
self.assertEqual(50, Bookmark.objects.filter(is_archived=False, title__startswith='foo').count())
|
Bookmark.objects.filter(
|
||||||
self.assertEqual(0, Bookmark.objects.filter(is_archived=True, title__startswith='foo').count())
|
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):
|
def test_select_all_toggles_all_checkboxes(self):
|
||||||
self.setup_numbered_bookmarks(5)
|
self.setup_numbered_bookmarks(5)
|
||||||
|
|
||||||
with sync_playwright() as p:
|
with sync_playwright() as p:
|
||||||
url = reverse('bookmarks:index')
|
url = reverse("bookmarks:index")
|
||||||
page = self.open(url, p)
|
page = self.open(url, p)
|
||||||
|
|
||||||
self.locate_bulk_edit_toggle().click()
|
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())
|
self.assertEqual(6, checkboxes.count())
|
||||||
for i in range(checkboxes.count()):
|
for i in range(checkboxes.count()):
|
||||||
expect(checkboxes.nth(i)).not_to_be_checked()
|
expect(checkboxes.nth(i)).not_to_be_checked()
|
||||||
|
@ -121,7 +201,7 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase):
|
||||||
self.setup_numbered_bookmarks(5)
|
self.setup_numbered_bookmarks(5)
|
||||||
|
|
||||||
with sync_playwright() as p:
|
with sync_playwright() as p:
|
||||||
url = reverse('bookmarks:index')
|
url = reverse("bookmarks:index")
|
||||||
self.open(url, p)
|
self.open(url, p)
|
||||||
|
|
||||||
self.locate_bulk_edit_toggle().click()
|
self.locate_bulk_edit_toggle().click()
|
||||||
|
@ -138,7 +218,7 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase):
|
||||||
self.setup_numbered_bookmarks(5)
|
self.setup_numbered_bookmarks(5)
|
||||||
|
|
||||||
with sync_playwright() as p:
|
with sync_playwright() as p:
|
||||||
url = reverse('bookmarks:index')
|
url = reverse("bookmarks:index")
|
||||||
self.open(url, p)
|
self.open(url, p)
|
||||||
|
|
||||||
self.locate_bulk_edit_toggle().click()
|
self.locate_bulk_edit_toggle().click()
|
||||||
|
@ -160,7 +240,7 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase):
|
||||||
self.setup_numbered_bookmarks(5)
|
self.setup_numbered_bookmarks(5)
|
||||||
|
|
||||||
with sync_playwright() as p:
|
with sync_playwright() as p:
|
||||||
url = reverse('bookmarks:index')
|
url = reverse("bookmarks:index")
|
||||||
self.open(url, p)
|
self.open(url, p)
|
||||||
|
|
||||||
self.locate_bulk_edit_toggle().click()
|
self.locate_bulk_edit_toggle().click()
|
||||||
|
@ -171,18 +251,22 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase):
|
||||||
expect(self.locate_bulk_edit_select_across()).to_be_checked()
|
expect(self.locate_bulk_edit_select_across()).to_be_checked()
|
||||||
|
|
||||||
# Hide select across by toggling a single bookmark
|
# 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()
|
expect(self.locate_bulk_edit_select_across()).not_to_be_visible()
|
||||||
|
|
||||||
# Show select across again, verify it is unchecked
|
# 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()
|
expect(self.locate_bulk_edit_select_across()).not_to_be_checked()
|
||||||
|
|
||||||
def test_execute_resets_all_checkboxes(self):
|
def test_execute_resets_all_checkboxes(self):
|
||||||
self.setup_numbered_bookmarks(100)
|
self.setup_numbered_bookmarks(100)
|
||||||
|
|
||||||
with sync_playwright() as p:
|
with sync_playwright() as p:
|
||||||
url = reverse('bookmarks:index')
|
url = reverse("bookmarks:index")
|
||||||
page = self.open(url, p)
|
page = self.open(url, p)
|
||||||
|
|
||||||
# Select all bookmarks, enable select across
|
# Select all bookmarks, enable select across
|
||||||
|
@ -191,18 +275,18 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase):
|
||||||
self.locate_bulk_edit_select_across().click()
|
self.locate_bulk_edit_select_across().click()
|
||||||
|
|
||||||
# Get reference for bookmark list
|
# Get reference for bookmark list
|
||||||
bookmark_list = page.locator('ul[ld-bookmark-list]')
|
bookmark_list = page.locator("ul[ld-bookmark-list]")
|
||||||
|
|
||||||
# Execute bulk action
|
# Execute bulk action
|
||||||
self.select_bulk_action('Mark as unread')
|
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("Execute").click()
|
||||||
self.locate_bulk_edit_bar().get_by_text('Confirm').click()
|
self.locate_bulk_edit_bar().get_by_text("Confirm").click()
|
||||||
|
|
||||||
# Wait until bookmark list is updated (old reference becomes invisible)
|
# Wait until bookmark list is updated (old reference becomes invisible)
|
||||||
expect(bookmark_list).not_to_be_visible()
|
expect(bookmark_list).not_to_be_visible()
|
||||||
|
|
||||||
# Verify bulk edit checkboxes are reset
|
# 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())
|
self.assertEqual(31, checkboxes.count())
|
||||||
for i in range(checkboxes.count()):
|
for i in range(checkboxes.count()):
|
||||||
expect(checkboxes.nth(i)).not_to_be_checked()
|
expect(checkboxes.nth(i)).not_to_be_checked()
|
||||||
|
@ -215,18 +299,22 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase):
|
||||||
self.setup_numbered_bookmarks(100)
|
self.setup_numbered_bookmarks(100)
|
||||||
|
|
||||||
with sync_playwright() as p:
|
with sync_playwright() as p:
|
||||||
url = reverse('bookmarks:index')
|
url = reverse("bookmarks:index")
|
||||||
self.open(url, p)
|
self.open(url, p)
|
||||||
|
|
||||||
self.locate_bulk_edit_toggle().click()
|
self.locate_bulk_edit_toggle().click()
|
||||||
self.locate_bulk_edit_select_all().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.select_bulk_action("Delete")
|
||||||
self.locate_bulk_edit_bar().get_by_text('Execute').click()
|
self.locate_bulk_edit_bar().get_by_text("Execute").click()
|
||||||
self.locate_bulk_edit_bar().get_by_text('Confirm').click()
|
self.locate_bulk_edit_bar().get_by_text("Confirm").click()
|
||||||
|
|
||||||
self.locate_bulk_edit_select_all().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()
|
||||||
|
|
|
@ -16,13 +16,15 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase):
|
||||||
# verify correct data is loaded on update
|
# verify correct data is loaded on update
|
||||||
self.setup_numbered_bookmarks(3, with_tags=True)
|
self.setup_numbered_bookmarks(3, with_tags=True)
|
||||||
self.setup_numbered_bookmarks(3, with_tags=True, archived=True)
|
self.setup_numbered_bookmarks(3, with_tags=True, archived=True)
|
||||||
self.setup_numbered_bookmarks(3,
|
self.setup_numbered_bookmarks(
|
||||||
|
3,
|
||||||
shared=True,
|
shared=True,
|
||||||
prefix="Joe's Bookmark",
|
prefix="Joe's Bookmark",
|
||||||
user=self.setup_user(enable_sharing=True))
|
user=self.setup_user(enable_sharing=True),
|
||||||
|
)
|
||||||
|
|
||||||
def assertVisibleBookmarks(self, titles: List[str]):
|
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))
|
expect(bookmark_tags).to_have_count(len(titles))
|
||||||
|
|
||||||
for title in titles:
|
for title in titles:
|
||||||
|
@ -30,7 +32,7 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase):
|
||||||
expect(matching_tag).to_be_visible()
|
expect(matching_tag).to_be_visible()
|
||||||
|
|
||||||
def assertVisibleTags(self, titles: List[str]):
|
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))
|
expect(tag_tags).to_have_count(len(titles))
|
||||||
|
|
||||||
for title in titles:
|
for title in titles:
|
||||||
|
@ -38,65 +40,67 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase):
|
||||||
expect(matching_tag).to_be_visible()
|
expect(matching_tag).to_be_visible()
|
||||||
|
|
||||||
def test_partial_update_respects_query(self):
|
def test_partial_update_respects_query(self):
|
||||||
self.setup_numbered_bookmarks(5, prefix='foo')
|
self.setup_numbered_bookmarks(5, prefix="foo")
|
||||||
self.setup_numbered_bookmarks(5, prefix='bar')
|
self.setup_numbered_bookmarks(5, prefix="bar")
|
||||||
|
|
||||||
with sync_playwright() as p:
|
with sync_playwright() as p:
|
||||||
url = reverse('bookmarks:index') + '?q=foo'
|
url = reverse("bookmarks:index") + "?q=foo"
|
||||||
self.open(url, p)
|
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.locate_bookmark("foo 2").get_by_text("Archive").click()
|
||||||
self.assertVisibleBookmarks(['foo 1', 'foo 3', 'foo 4', 'foo 5'])
|
self.assertVisibleBookmarks(["foo 1", "foo 3", "foo 4", "foo 5"])
|
||||||
|
|
||||||
def test_partial_update_respects_sort(self):
|
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:
|
with sync_playwright() as p:
|
||||||
url = reverse('bookmarks:index') + '?sort=title_asc'
|
url = reverse("bookmarks:index") + "?sort=title_asc"
|
||||||
page = self.open(url, p)
|
page = self.open(url, p)
|
||||||
|
|
||||||
first_item = page.locator('li[ld-bookmark-item]').first
|
first_item = page.locator("li[ld-bookmark-item]").first
|
||||||
expect(first_item).to_contain_text('foo 1')
|
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
|
first_item = page.locator("li[ld-bookmark-item]").first
|
||||||
expect(first_item).to_contain_text('foo 2')
|
expect(first_item).to_contain_text("foo 2")
|
||||||
|
|
||||||
def test_partial_update_respects_page(self):
|
def test_partial_update_respects_page(self):
|
||||||
# add a suffix, otherwise 'foo 1' also matches 'foo 10'
|
# 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:
|
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)
|
self.open(url, p)
|
||||||
|
|
||||||
# with descending sort, page two has 'foo 1' to 'foo 20'
|
# 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.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)
|
self.assertVisibleBookmarks(expected_titles)
|
||||||
|
|
||||||
def test_multiple_partial_updates(self):
|
def test_multiple_partial_updates(self):
|
||||||
self.setup_numbered_bookmarks(5)
|
self.setup_numbered_bookmarks(5)
|
||||||
|
|
||||||
with sync_playwright() as p:
|
with sync_playwright() as p:
|
||||||
url = reverse('bookmarks:index')
|
url = reverse("bookmarks:index")
|
||||||
self.open(url, p)
|
self.open(url, p)
|
||||||
|
|
||||||
self.locate_bookmark('Bookmark 1').get_by_text('Archive').click()
|
self.locate_bookmark("Bookmark 1").get_by_text("Archive").click()
|
||||||
self.assertVisibleBookmarks(['Bookmark 2', 'Bookmark 3', 'Bookmark 4', 'Bookmark 5'])
|
self.assertVisibleBookmarks(
|
||||||
|
["Bookmark 2", "Bookmark 3", "Bookmark 4", "Bookmark 5"]
|
||||||
|
)
|
||||||
|
|
||||||
self.locate_bookmark('Bookmark 2').get_by_text('Archive').click()
|
self.locate_bookmark("Bookmark 2").get_by_text("Archive").click()
|
||||||
self.assertVisibleBookmarks(['Bookmark 3', 'Bookmark 4', 'Bookmark 5'])
|
self.assertVisibleBookmarks(["Bookmark 3", "Bookmark 4", "Bookmark 5"])
|
||||||
|
|
||||||
self.locate_bookmark('Bookmark 3').get_by_text('Archive').click()
|
self.locate_bookmark("Bookmark 3").get_by_text("Archive").click()
|
||||||
self.assertVisibleBookmarks(['Bookmark 4', 'Bookmark 5'])
|
self.assertVisibleBookmarks(["Bookmark 4", "Bookmark 5"])
|
||||||
|
|
||||||
self.assertReloads(0)
|
self.assertReloads(0)
|
||||||
|
|
||||||
|
@ -104,185 +108,201 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase):
|
||||||
self.setup_fixture()
|
self.setup_fixture()
|
||||||
|
|
||||||
with sync_playwright() as p:
|
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.assertVisibleBookmarks(["Bookmark 1", "Bookmark 3"])
|
||||||
self.assertVisibleTags(['Tag 1', 'Tag 3'])
|
self.assertVisibleTags(["Tag 1", "Tag 3"])
|
||||||
self.assertReloads(0)
|
self.assertReloads(0)
|
||||||
|
|
||||||
def test_active_bookmarks_partial_update_on_delete(self):
|
def test_active_bookmarks_partial_update_on_delete(self):
|
||||||
self.setup_fixture()
|
self.setup_fixture()
|
||||||
|
|
||||||
with sync_playwright() as p:
|
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("Remove").click()
|
||||||
self.locate_bookmark('Bookmark 2').get_by_text('Confirm').click()
|
self.locate_bookmark("Bookmark 2").get_by_text("Confirm").click()
|
||||||
|
|
||||||
self.assertVisibleBookmarks(['Bookmark 1', 'Bookmark 3'])
|
self.assertVisibleBookmarks(["Bookmark 1", "Bookmark 3"])
|
||||||
self.assertVisibleTags(['Tag 1', 'Tag 3'])
|
self.assertVisibleTags(["Tag 1", "Tag 3"])
|
||||||
self.assertReloads(0)
|
self.assertReloads(0)
|
||||||
|
|
||||||
def test_active_bookmarks_partial_update_on_mark_as_read(self):
|
def test_active_bookmarks_partial_update_on_mark_as_read(self):
|
||||||
self.setup_fixture()
|
self.setup_fixture()
|
||||||
bookmark2 = self.get_numbered_bookmark('Bookmark 2')
|
bookmark2 = self.get_numbered_bookmark("Bookmark 2")
|
||||||
bookmark2.unread = True
|
bookmark2.unread = True
|
||||||
bookmark2.save()
|
bookmark2.save()
|
||||||
|
|
||||||
with sync_playwright() as p:
|
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')
|
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("Unread").click()
|
||||||
self.locate_bookmark('Bookmark 2').get_by_text('Yes').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)
|
self.assertReloads(0)
|
||||||
|
|
||||||
def test_active_bookmarks_partial_update_on_unshare(self):
|
def test_active_bookmarks_partial_update_on_unshare(self):
|
||||||
self.setup_fixture()
|
self.setup_fixture()
|
||||||
bookmark2 = self.get_numbered_bookmark('Bookmark 2')
|
bookmark2 = self.get_numbered_bookmark("Bookmark 2")
|
||||||
bookmark2.shared = True
|
bookmark2.shared = True
|
||||||
bookmark2.save()
|
bookmark2.save()
|
||||||
|
|
||||||
with sync_playwright() as p:
|
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')
|
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("Shared").click()
|
||||||
self.locate_bookmark('Bookmark 2').get_by_text('Yes').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)
|
self.assertReloads(0)
|
||||||
|
|
||||||
def test_active_bookmarks_partial_update_on_bulk_archive(self):
|
def test_active_bookmarks_partial_update_on_bulk_archive(self):
|
||||||
self.setup_fixture()
|
self.setup_fixture()
|
||||||
|
|
||||||
with sync_playwright() as p:
|
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_toggle().click()
|
||||||
self.locate_bookmark('Bookmark 2').locator('label[ld-bulk-edit-checkbox]').click()
|
self.locate_bookmark("Bookmark 2").locator(
|
||||||
self.select_bulk_action('Archive')
|
"label[ld-bulk-edit-checkbox]"
|
||||||
self.locate_bulk_edit_bar().get_by_text('Execute').click()
|
).click()
|
||||||
self.locate_bulk_edit_bar().get_by_text('Confirm').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.assertVisibleBookmarks(["Bookmark 1", "Bookmark 3"])
|
||||||
self.assertVisibleTags(['Tag 1', 'Tag 3'])
|
self.assertVisibleTags(["Tag 1", "Tag 3"])
|
||||||
self.assertReloads(0)
|
self.assertReloads(0)
|
||||||
|
|
||||||
def test_active_bookmarks_partial_update_on_bulk_delete(self):
|
def test_active_bookmarks_partial_update_on_bulk_delete(self):
|
||||||
self.setup_fixture()
|
self.setup_fixture()
|
||||||
|
|
||||||
with sync_playwright() as p:
|
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_toggle().click()
|
||||||
self.locate_bookmark('Bookmark 2').locator('label[ld-bulk-edit-checkbox]').click()
|
self.locate_bookmark("Bookmark 2").locator(
|
||||||
self.select_bulk_action('Delete')
|
"label[ld-bulk-edit-checkbox]"
|
||||||
self.locate_bulk_edit_bar().get_by_text('Execute').click()
|
).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.assertVisibleBookmarks(['Bookmark 1', 'Bookmark 3'])
|
self.assertVisibleBookmarks(["Bookmark 1", "Bookmark 3"])
|
||||||
self.assertVisibleTags(['Tag 1', 'Tag 3'])
|
self.assertVisibleTags(["Tag 1", "Tag 3"])
|
||||||
self.assertReloads(0)
|
self.assertReloads(0)
|
||||||
|
|
||||||
def test_archived_bookmarks_partial_update_on_unarchive(self):
|
def test_archived_bookmarks_partial_update_on_unarchive(self):
|
||||||
self.setup_fixture()
|
self.setup_fixture()
|
||||||
|
|
||||||
with sync_playwright() as p:
|
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.assertVisibleBookmarks(["Archived Bookmark 1", "Archived Bookmark 3"])
|
||||||
self.assertVisibleTags(['Archived Tag 1', 'Archived Tag 3'])
|
self.assertVisibleTags(["Archived Tag 1", "Archived Tag 3"])
|
||||||
self.assertReloads(0)
|
self.assertReloads(0)
|
||||||
|
|
||||||
def test_archived_bookmarks_partial_update_on_delete(self):
|
def test_archived_bookmarks_partial_update_on_delete(self):
|
||||||
self.setup_fixture()
|
self.setup_fixture()
|
||||||
|
|
||||||
with sync_playwright() as p:
|
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("Remove").click()
|
||||||
self.locate_bookmark('Archived Bookmark 2').get_by_text('Confirm').click()
|
self.locate_bookmark("Archived Bookmark 2").get_by_text("Confirm").click()
|
||||||
|
|
||||||
self.assertVisibleBookmarks(['Archived Bookmark 1', 'Archived Bookmark 3'])
|
self.assertVisibleBookmarks(["Archived Bookmark 1", "Archived Bookmark 3"])
|
||||||
self.assertVisibleTags(['Archived Tag 1', 'Archived Tag 3'])
|
self.assertVisibleTags(["Archived Tag 1", "Archived Tag 3"])
|
||||||
self.assertReloads(0)
|
self.assertReloads(0)
|
||||||
|
|
||||||
def test_archived_bookmarks_partial_update_on_bulk_unarchive(self):
|
def test_archived_bookmarks_partial_update_on_bulk_unarchive(self):
|
||||||
self.setup_fixture()
|
self.setup_fixture()
|
||||||
|
|
||||||
with sync_playwright() as p:
|
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_toggle().click()
|
||||||
self.locate_bookmark('Archived Bookmark 2').locator('label[ld-bulk-edit-checkbox]').click()
|
self.locate_bookmark("Archived Bookmark 2").locator(
|
||||||
self.select_bulk_action('Unarchive')
|
"label[ld-bulk-edit-checkbox]"
|
||||||
self.locate_bulk_edit_bar().get_by_text('Execute').click()
|
).click()
|
||||||
self.locate_bulk_edit_bar().get_by_text('Confirm').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.assertVisibleBookmarks(["Archived Bookmark 1", "Archived Bookmark 3"])
|
||||||
self.assertVisibleTags(['Archived Tag 1', 'Archived Tag 3'])
|
self.assertVisibleTags(["Archived Tag 1", "Archived Tag 3"])
|
||||||
self.assertReloads(0)
|
self.assertReloads(0)
|
||||||
|
|
||||||
def test_archived_bookmarks_partial_update_on_bulk_delete(self):
|
def test_archived_bookmarks_partial_update_on_bulk_delete(self):
|
||||||
self.setup_fixture()
|
self.setup_fixture()
|
||||||
|
|
||||||
with sync_playwright() as p:
|
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_toggle().click()
|
||||||
self.locate_bookmark('Archived Bookmark 2').locator('label[ld-bulk-edit-checkbox]').click()
|
self.locate_bookmark("Archived Bookmark 2").locator(
|
||||||
self.select_bulk_action('Delete')
|
"label[ld-bulk-edit-checkbox]"
|
||||||
self.locate_bulk_edit_bar().get_by_text('Execute').click()
|
).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.assertVisibleBookmarks(['Archived Bookmark 1', 'Archived Bookmark 3'])
|
self.assertVisibleBookmarks(["Archived Bookmark 1", "Archived Bookmark 3"])
|
||||||
self.assertVisibleTags(['Archived Tag 1', 'Archived Tag 3'])
|
self.assertVisibleTags(["Archived Tag 1", "Archived Tag 3"])
|
||||||
self.assertReloads(0)
|
self.assertReloads(0)
|
||||||
|
|
||||||
def test_shared_bookmarks_partial_update_on_unarchive(self):
|
def test_shared_bookmarks_partial_update_on_unarchive(self):
|
||||||
self.setup_fixture()
|
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:
|
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
|
# Shared bookmarks page also shows archived bookmarks, though it probably shouldn't
|
||||||
self.assertVisibleBookmarks([
|
self.assertVisibleBookmarks(
|
||||||
'My Bookmark 1',
|
[
|
||||||
'My Bookmark 2',
|
"My Bookmark 1",
|
||||||
'My Bookmark 3',
|
"My Bookmark 2",
|
||||||
|
"My Bookmark 3",
|
||||||
"Joe's Bookmark 1",
|
"Joe's Bookmark 1",
|
||||||
"Joe's Bookmark 2",
|
"Joe's Bookmark 2",
|
||||||
"Joe's Bookmark 3",
|
"Joe's Bookmark 3",
|
||||||
])
|
]
|
||||||
self.assertVisibleTags(['Shared Tag 1', 'Shared Tag 2', 'Shared Tag 3'])
|
)
|
||||||
|
self.assertVisibleTags(["Shared Tag 1", "Shared Tag 2", "Shared Tag 3"])
|
||||||
self.assertReloads(0)
|
self.assertReloads(0)
|
||||||
|
|
||||||
def test_shared_bookmarks_partial_update_on_delete(self):
|
def test_shared_bookmarks_partial_update_on_delete(self):
|
||||||
self.setup_fixture()
|
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:
|
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("Remove").click()
|
||||||
self.locate_bookmark('My Bookmark 2').get_by_text('Confirm').click()
|
self.locate_bookmark("My Bookmark 2").get_by_text("Confirm").click()
|
||||||
|
|
||||||
self.assertVisibleBookmarks([
|
self.assertVisibleBookmarks(
|
||||||
'My Bookmark 1',
|
[
|
||||||
'My Bookmark 3',
|
"My Bookmark 1",
|
||||||
|
"My Bookmark 3",
|
||||||
"Joe's Bookmark 1",
|
"Joe's Bookmark 1",
|
||||||
"Joe's Bookmark 2",
|
"Joe's Bookmark 2",
|
||||||
"Joe's Bookmark 3",
|
"Joe's Bookmark 3",
|
||||||
])
|
]
|
||||||
self.assertVisibleTags(['Shared Tag 1', 'Shared Tag 3'])
|
)
|
||||||
|
self.assertVisibleTags(["Shared Tag 1", "Shared Tag 3"])
|
||||||
self.assertReloads(0)
|
self.assertReloads(0)
|
||||||
|
|
|
@ -9,11 +9,11 @@ class GlobalShortcutsE2ETestCase(LinkdingE2ETestCase):
|
||||||
with sync_playwright() as p:
|
with sync_playwright() as p:
|
||||||
browser = self.setup_browser(p)
|
browser = self.setup_browser(p)
|
||||||
page = browser.new_page()
|
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()
|
browser.close()
|
||||||
|
|
||||||
|
@ -21,10 +21,10 @@ class GlobalShortcutsE2ETestCase(LinkdingE2ETestCase):
|
||||||
with sync_playwright() as p:
|
with sync_playwright() as p:
|
||||||
browser = self.setup_browser(p)
|
browser = self.setup_browser(p)
|
||||||
page = browser.new_page()
|
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()
|
browser.close()
|
||||||
|
|
|
@ -9,12 +9,14 @@ class SettingsGeneralE2ETestCase(LinkdingE2ETestCase):
|
||||||
with sync_playwright() as p:
|
with sync_playwright() as p:
|
||||||
browser = self.setup_browser(p)
|
browser = self.setup_browser(p)
|
||||||
page = browser.new_page()
|
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 = page.get_by_label("Enable bookmark sharing")
|
||||||
enable_sharing_label = page.get_by_text('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 = page.get_by_label("Enable public bookmark sharing")
|
||||||
enable_public_sharing_label = page.get_by_text('Enable public bookmark sharing')
|
enable_public_sharing_label = page.get_by_text(
|
||||||
|
"Enable public bookmark sharing"
|
||||||
|
)
|
||||||
|
|
||||||
# Public sharing is disabled by default
|
# Public sharing is disabled by default
|
||||||
expect(enable_sharing).not_to_be_checked()
|
expect(enable_sharing).not_to_be_checked()
|
||||||
|
|
|
@ -7,24 +7,28 @@ from bookmarks.tests.helpers import BookmarkFactoryMixin
|
||||||
class LinkdingE2ETestCase(LiveServerTestCase, BookmarkFactoryMixin):
|
class LinkdingE2ETestCase(LiveServerTestCase, BookmarkFactoryMixin):
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
self.client.force_login(self.get_or_create_test_user())
|
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:
|
def setup_browser(self, playwright) -> BrowserContext:
|
||||||
browser = playwright.chromium.launch(headless=True)
|
browser = playwright.chromium.launch(headless=True)
|
||||||
context = browser.new_context()
|
context = browser.new_context()
|
||||||
context.add_cookies([{
|
context.add_cookies(
|
||||||
'name': 'sessionid',
|
[
|
||||||
'value': self.cookie.value,
|
{
|
||||||
'domain': self.live_server_url.replace('http:', ''),
|
"name": "sessionid",
|
||||||
'path': '/'
|
"value": self.cookie.value,
|
||||||
}])
|
"domain": self.live_server_url.replace("http:", ""),
|
||||||
|
"path": "/",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def open(self, url: str, playwright: Playwright) -> Page:
|
def open(self, url: str, playwright: Playwright) -> Page:
|
||||||
browser = self.setup_browser(playwright)
|
browser = self.setup_browser(playwright)
|
||||||
self.page = browser.new_page()
|
self.page = browser.new_page()
|
||||||
self.page.goto(self.live_server_url + url)
|
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
|
self.num_loads = 0
|
||||||
return self.page
|
return self.page
|
||||||
|
|
||||||
|
@ -35,20 +39,24 @@ class LinkdingE2ETestCase(LiveServerTestCase, BookmarkFactoryMixin):
|
||||||
self.assertEqual(self.num_loads, count)
|
self.assertEqual(self.num_loads, count)
|
||||||
|
|
||||||
def locate_bookmark(self, title: str):
|
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)
|
return bookmark_tags.filter(has_text=title)
|
||||||
|
|
||||||
def locate_bulk_edit_bar(self):
|
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):
|
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):
|
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):
|
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):
|
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)
|
||||||
|
)
|
||||||
|
|
|
@ -17,17 +17,21 @@ class FeedContext:
|
||||||
|
|
||||||
def sanitize(text: str):
|
def sanitize(text: str):
|
||||||
if not text:
|
if not text:
|
||||||
return ''
|
return ""
|
||||||
# remove control characters
|
# remove control characters
|
||||||
valid_chars = ['\n', '\r', '\t']
|
valid_chars = ["\n", "\r", "\t"]
|
||||||
return ''.join(ch for ch in text if ch in valid_chars or unicodedata.category(ch)[0] != 'C')
|
return "".join(
|
||||||
|
ch for ch in text if ch in valid_chars or unicodedata.category(ch)[0] != "C"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class BaseBookmarksFeed(Feed):
|
class BaseBookmarksFeed(Feed):
|
||||||
def get_object(self, request, feed_key: str):
|
def get_object(self, request, feed_key: str):
|
||||||
feed_token = FeedToken.objects.get(key__exact=feed_key)
|
feed_token = FeedToken.objects.get(key__exact=feed_key)
|
||||||
search = BookmarkSearch(q=request.GET.get('q', ''))
|
search = BookmarkSearch(q=request.GET.get("q", ""))
|
||||||
query_set = queries.query_bookmarks(feed_token.user, feed_token.user.profile, search)
|
query_set = queries.query_bookmarks(
|
||||||
|
feed_token.user, feed_token.user.profile, search
|
||||||
|
)
|
||||||
return FeedContext(feed_token, query_set)
|
return FeedContext(feed_token, query_set)
|
||||||
|
|
||||||
def item_title(self, item: Bookmark):
|
def item_title(self, item: Bookmark):
|
||||||
|
@ -44,22 +48,22 @@ class BaseBookmarksFeed(Feed):
|
||||||
|
|
||||||
|
|
||||||
class AllBookmarksFeed(BaseBookmarksFeed):
|
class AllBookmarksFeed(BaseBookmarksFeed):
|
||||||
title = 'All bookmarks'
|
title = "All bookmarks"
|
||||||
description = 'All bookmarks'
|
description = "All bookmarks"
|
||||||
|
|
||||||
def link(self, context: FeedContext):
|
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):
|
def items(self, context: FeedContext):
|
||||||
return context.query_set
|
return context.query_set
|
||||||
|
|
||||||
|
|
||||||
class UnreadBookmarksFeed(BaseBookmarksFeed):
|
class UnreadBookmarksFeed(BaseBookmarksFeed):
|
||||||
title = 'Unread bookmarks'
|
title = "Unread bookmarks"
|
||||||
description = 'All unread bookmarks'
|
description = "All unread bookmarks"
|
||||||
|
|
||||||
def link(self, context: FeedContext):
|
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):
|
def items(self, context: FeedContext):
|
||||||
return context.query_set.filter(unread=True)
|
return context.query_set.filter(unread=True)
|
||||||
|
|
|
@ -8,19 +8,19 @@ class Command(BaseCommand):
|
||||||
help = "Creates a backup of the linkding database"
|
help = "Creates a backup of the linkding database"
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
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):
|
def handle(self, *args, **options):
|
||||||
destination = options['destination']
|
destination = options["destination"]
|
||||||
|
|
||||||
def progress(status, remaining, total):
|
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)
|
backup_db = sqlite3.connect(destination)
|
||||||
with backup_db:
|
with backup_db:
|
||||||
source_db.backup(backup_db, pages=50, progress=progress)
|
source_db.backup(backup_db, pages=50, progress=progress)
|
||||||
backup_db.close()
|
backup_db.close()
|
||||||
source_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}"))
|
||||||
|
|
|
@ -12,18 +12,20 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
superuser_name = os.getenv('LD_SUPERUSER_NAME', None)
|
superuser_name = os.getenv("LD_SUPERUSER_NAME", None)
|
||||||
superuser_password = os.getenv('LD_SUPERUSER_PASSWORD', None)
|
superuser_password = os.getenv("LD_SUPERUSER_PASSWORD", None)
|
||||||
|
|
||||||
# Skip if option is undefined
|
# Skip if option is undefined
|
||||||
if not superuser_name:
|
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
|
return
|
||||||
|
|
||||||
# Skip if user already exists
|
# Skip if user already exists
|
||||||
user_exists = User.objects.filter(username=superuser_name).exists()
|
user_exists = User.objects.filter(username=superuser_name).exists()
|
||||||
if user_exists:
|
if user_exists:
|
||||||
logger.info('Skip creating initial superuser, user already exists')
|
logger.info("Skip creating initial superuser, user already exists")
|
||||||
return
|
return
|
||||||
|
|
||||||
user = User(username=superuser_name, is_superuser=True, is_staff=True)
|
user = User(username=superuser_name, is_superuser=True, is_staff=True)
|
||||||
|
@ -34,4 +36,4 @@ class Command(BaseCommand):
|
||||||
user.set_unusable_password()
|
user.set_unusable_password()
|
||||||
|
|
||||||
user.save()
|
user.save()
|
||||||
logger.info('Created initial superuser')
|
logger.info("Created initial superuser")
|
||||||
|
|
|
@ -14,11 +14,11 @@ class Command(BaseCommand):
|
||||||
if not settings.USE_SQLITE:
|
if not settings.USE_SQLITE:
|
||||||
return
|
return
|
||||||
|
|
||||||
connection = connections['default']
|
connection = connections["default"]
|
||||||
with connection.cursor() as cursor:
|
with connection.cursor() as cursor:
|
||||||
cursor.execute("PRAGMA journal_mode")
|
cursor.execute("PRAGMA journal_mode")
|
||||||
current_mode = cursor.fetchone()[0]
|
current_mode = cursor.fetchone()[0]
|
||||||
logger.info(f'Current journal mode: {current_mode}')
|
logger.info(f"Current journal mode: {current_mode}")
|
||||||
if current_mode != 'wal':
|
if current_mode != "wal":
|
||||||
cursor.execute("PRAGMA journal_mode=wal;")
|
cursor.execute("PRAGMA journal_mode=wal;")
|
||||||
logger.info('Switched to WAL journal mode')
|
logger.info("Switched to WAL journal mode")
|
||||||
|
|
|
@ -6,13 +6,15 @@ class Command(BaseCommand):
|
||||||
help = "Creates an admin user non-interactively if it doesn't exist"
|
help = "Creates an admin user non-interactively if it doesn't exist"
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
def add_arguments(self, parser):
|
||||||
parser.add_argument('--username', help="Admin's username")
|
parser.add_argument("--username", help="Admin's username")
|
||||||
parser.add_argument('--email', help="Admin's email")
|
parser.add_argument("--email", help="Admin's email")
|
||||||
parser.add_argument('--password', help="Admin's password")
|
parser.add_argument("--password", help="Admin's password")
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
if not User.objects.filter(username=options['username']).exists():
|
if not User.objects.filter(username=options["username"]).exists():
|
||||||
User.objects.create_superuser(username=options['username'],
|
User.objects.create_superuser(
|
||||||
email=options['email'],
|
username=options["username"],
|
||||||
password=options['password'])
|
email=options["email"],
|
||||||
|
password=options["password"],
|
||||||
|
)
|
||||||
|
|
|
@ -5,15 +5,17 @@ from bookmarks.services.importer import import_netscape_html
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = 'Import Netscape HTML bookmark file'
|
help = "Import Netscape HTML bookmark file"
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
def add_arguments(self, parser):
|
||||||
parser.add_argument('file', type=str, help='Path to file')
|
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(
|
||||||
|
"user", type=str, help="Name of the user for which to import"
|
||||||
|
)
|
||||||
|
|
||||||
def handle(self, *args, **kwargs):
|
def handle(self, *args, **kwargs):
|
||||||
filepath = kwargs['file']
|
filepath = kwargs["file"]
|
||||||
username = kwargs['user']
|
username = kwargs["user"]
|
||||||
with open(filepath) as html_file:
|
with open(filepath) as html_file:
|
||||||
html = html_file.read()
|
html = html_file.read()
|
||||||
user = User.objects.get(username=username)
|
user = User.objects.get(username=username)
|
||||||
|
|
|
@ -15,19 +15,36 @@ class Migration(migrations.Migration):
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Bookmark',
|
name="Bookmark",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
(
|
||||||
('url', models.URLField()),
|
"id",
|
||||||
('title', models.CharField(max_length=512)),
|
models.AutoField(
|
||||||
('description', models.TextField()),
|
auto_created=True,
|
||||||
('website_title', models.CharField(blank=True, max_length=512, null=True)),
|
primary_key=True,
|
||||||
('website_description', models.TextField(blank=True, null=True)),
|
serialize=False,
|
||||||
('unread', models.BooleanField(default=True)),
|
verbose_name="ID",
|
||||||
('date_added', models.DateTimeField()),
|
),
|
||||||
('date_modified', models.DateTimeField()),
|
),
|
||||||
('date_accessed', models.DateTimeField(blank=True, null=True)),
|
("url", models.URLField()),
|
||||||
('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
("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,
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -9,22 +9,36 @@ class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
('bookmarks', '0001_initial'),
|
("bookmarks", "0001_initial"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Tag',
|
name="Tag",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
(
|
||||||
('name', models.CharField(max_length=64)),
|
"id",
|
||||||
('date_added', models.DateTimeField()),
|
models.AutoField(
|
||||||
('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
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(
|
migrations.AddField(
|
||||||
model_name='bookmark',
|
model_name="bookmark",
|
||||||
name='tags',
|
name="tags",
|
||||||
field=models.ManyToManyField(to='bookmarks.Tag'),
|
field=models.ManyToManyField(to="bookmarks.Tag"),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,13 +6,13 @@ from django.db import migrations, models
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('bookmarks', '0002_auto_20190629_2303'),
|
("bookmarks", "0002_auto_20190629_2303"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='bookmark',
|
model_name="bookmark",
|
||||||
name='url',
|
name="url",
|
||||||
field=models.URLField(max_length=2048),
|
field=models.URLField(max_length=2048),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,18 +6,18 @@ from django.db import migrations, models
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('bookmarks', '0003_auto_20200913_0656'),
|
("bookmarks", "0003_auto_20200913_0656"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='bookmark',
|
model_name="bookmark",
|
||||||
name='description',
|
name="description",
|
||||||
field=models.TextField(blank=True),
|
field=models.TextField(blank=True),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='bookmark',
|
model_name="bookmark",
|
||||||
name='title',
|
name="title",
|
||||||
field=models.CharField(blank=True, max_length=512),
|
field=models.CharField(blank=True, max_length=512),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -7,13 +7,16 @@ from django.db import migrations, models
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('bookmarks', '0004_auto_20200926_1028'),
|
("bookmarks", "0004_auto_20200926_1028"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='bookmark',
|
model_name="bookmark",
|
||||||
name='url',
|
name="url",
|
||||||
field=models.CharField(max_length=2048, validators=[bookmarks.validators.BookmarkURLValidator()]),
|
field=models.CharField(
|
||||||
|
max_length=2048,
|
||||||
|
validators=[bookmarks.validators.BookmarkURLValidator()],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,13 +6,13 @@ from django.db import migrations, models
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('bookmarks', '0005_auto_20210103_1212'),
|
("bookmarks", "0005_auto_20210103_1212"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='bookmark',
|
model_name="bookmark",
|
||||||
name='is_archived',
|
name="is_archived",
|
||||||
field=models.BooleanField(default=False),
|
field=models.BooleanField(default=False),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,8 +6,8 @@ import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
def forwards(apps, schema_editor):
|
def forwards(apps, schema_editor):
|
||||||
User = apps.get_model('auth', 'User')
|
User = apps.get_model("auth", "User")
|
||||||
UserProfile = apps.get_model('bookmarks', 'UserProfile')
|
UserProfile = apps.get_model("bookmarks", "UserProfile")
|
||||||
for user in User.objects.all():
|
for user in User.objects.all():
|
||||||
try:
|
try:
|
||||||
if user.profile:
|
if user.profile:
|
||||||
|
@ -24,19 +24,42 @@ def reverse(apps, schema_editor):
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
dependencies = [
|
dependencies = [
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
('bookmarks', '0006_bookmark_is_archived'),
|
("bookmarks", "0006_bookmark_is_archived"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='UserProfile',
|
name="UserProfile",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
(
|
||||||
('theme',
|
"id",
|
||||||
models.CharField(choices=[('auto', 'Auto'), ('light', 'Light'), ('dark', 'Dark')], default='auto',
|
models.AutoField(
|
||||||
max_length=10)),
|
auto_created=True,
|
||||||
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profile',
|
primary_key=True,
|
||||||
to=settings.AUTH_USER_MODEL)),
|
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),
|
migrations.RunPython(forwards, reverse),
|
||||||
|
|
|
@ -6,13 +6,21 @@ from django.db import migrations, models
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('bookmarks', '0007_userprofile'),
|
("bookmarks", "0007_userprofile"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='userprofile',
|
model_name="userprofile",
|
||||||
name='bookmark_date_display',
|
name="bookmark_date_display",
|
||||||
field=models.CharField(choices=[('relative', 'Relative'), ('absolute', 'Absolute'), ('hidden', 'Hidden')], default='relative', max_length=10),
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("relative", "Relative"),
|
||||||
|
("absolute", "Absolute"),
|
||||||
|
("hidden", "Hidden"),
|
||||||
|
],
|
||||||
|
default="relative",
|
||||||
|
max_length=10,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,13 +6,13 @@ from django.db import migrations, models
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('bookmarks', '0008_userprofile_bookmark_date_display'),
|
("bookmarks", "0008_userprofile_bookmark_date_display"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='bookmark',
|
model_name="bookmark",
|
||||||
name='web_archive_snapshot_url',
|
name="web_archive_snapshot_url",
|
||||||
field=models.CharField(blank=True, max_length=2048),
|
field=models.CharField(blank=True, max_length=2048),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,13 +6,17 @@ from django.db import migrations, models
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('bookmarks', '0009_bookmark_web_archive_snapshot_url'),
|
("bookmarks", "0009_bookmark_web_archive_snapshot_url"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='userprofile',
|
model_name="userprofile",
|
||||||
name='bookmark_link_target',
|
name="bookmark_link_target",
|
||||||
field=models.CharField(choices=[('_blank', 'New page'), ('_self', 'Same page')], default='_blank', max_length=10),
|
field=models.CharField(
|
||||||
|
choices=[("_blank", "New page"), ("_self", "Same page")],
|
||||||
|
default="_blank",
|
||||||
|
max_length=10,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,13 +6,17 @@ from django.db import migrations, models
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('bookmarks', '0010_userprofile_bookmark_link_target'),
|
("bookmarks", "0010_userprofile_bookmark_link_target"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='userprofile',
|
model_name="userprofile",
|
||||||
name='web_archive_integration',
|
name="web_archive_integration",
|
||||||
field=models.CharField(choices=[('disabled', 'Disabled'), ('enabled', 'Enabled')], default='disabled', max_length=10),
|
field=models.CharField(
|
||||||
|
choices=[("disabled", "Disabled"), ("enabled", "Enabled")],
|
||||||
|
default="disabled",
|
||||||
|
max_length=10,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -9,18 +9,32 @@ class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
('bookmarks', '0011_userprofile_web_archive_integration'),
|
("bookmarks", "0011_userprofile_web_archive_integration"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Toast',
|
name="Toast",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
(
|
||||||
('key', models.CharField(max_length=50)),
|
"id",
|
||||||
('message', models.TextField()),
|
models.AutoField(
|
||||||
('acknowledged', models.BooleanField(default=False)),
|
auto_created=True,
|
||||||
('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -10,19 +10,21 @@ User = get_user_model()
|
||||||
|
|
||||||
def forwards(apps, schema_editor):
|
def forwards(apps, schema_editor):
|
||||||
for user in User.objects.all():
|
for user in User.objects.all():
|
||||||
toast = Toast(key='web_archive_opt_in_hint',
|
toast = Toast(
|
||||||
message='The Internet Archive Wayback Machine integration has been disabled by default. Check the Settings to re-enable it.',
|
key="web_archive_opt_in_hint",
|
||||||
owner=user)
|
message="The Internet Archive Wayback Machine integration has been disabled by default. Check the Settings to re-enable it.",
|
||||||
|
owner=user,
|
||||||
|
)
|
||||||
toast.save()
|
toast.save()
|
||||||
|
|
||||||
|
|
||||||
def reverse(apps, schema_editor):
|
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):
|
class Migration(migrations.Migration):
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('bookmarks', '0012_toast'),
|
("bookmarks", "0012_toast"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
|
|
@ -4,7 +4,7 @@ from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
def forwards(apps, schema_editor):
|
def forwards(apps, schema_editor):
|
||||||
Bookmark = apps.get_model('bookmarks', 'Bookmark')
|
Bookmark = apps.get_model("bookmarks", "Bookmark")
|
||||||
Bookmark.objects.update(unread=False)
|
Bookmark.objects.update(unread=False)
|
||||||
|
|
||||||
|
|
||||||
|
@ -14,13 +14,13 @@ def reverse(apps, schema_editor):
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('bookmarks', '0013_web_archive_optin_toast'),
|
("bookmarks", "0013_web_archive_optin_toast"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='bookmark',
|
model_name="bookmark",
|
||||||
name='unread',
|
name="unread",
|
||||||
field=models.BooleanField(default=False),
|
field=models.BooleanField(default=False),
|
||||||
),
|
),
|
||||||
migrations.RunPython(forwards, reverse),
|
migrations.RunPython(forwards, reverse),
|
||||||
|
|
|
@ -9,16 +9,26 @@ class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
('bookmarks', '0014_alter_bookmark_unread'),
|
("bookmarks", "0014_alter_bookmark_unread"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='FeedToken',
|
name="FeedToken",
|
||||||
fields=[
|
fields=[
|
||||||
('key', models.CharField(max_length=40, primary_key=True, serialize=False)),
|
(
|
||||||
('created', models.DateTimeField(auto_now_add=True)),
|
"key",
|
||||||
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='feed_token', to=settings.AUTH_USER_MODEL)),
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,13 +6,13 @@ from django.db import migrations, models
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('bookmarks', '0015_feedtoken'),
|
("bookmarks", "0015_feedtoken"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='bookmark',
|
model_name="bookmark",
|
||||||
name='shared',
|
name="shared",
|
||||||
field=models.BooleanField(default=False),
|
field=models.BooleanField(default=False),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,13 +6,13 @@ from django.db import migrations, models
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('bookmarks', '0016_bookmark_shared'),
|
("bookmarks", "0016_bookmark_shared"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='userprofile',
|
model_name="userprofile",
|
||||||
name='enable_sharing',
|
name="enable_sharing",
|
||||||
field=models.BooleanField(default=False),
|
field=models.BooleanField(default=False),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,13 +6,13 @@ from django.db import migrations, models
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('bookmarks', '0017_userprofile_enable_sharing'),
|
("bookmarks", "0017_userprofile_enable_sharing"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='bookmark',
|
model_name="bookmark",
|
||||||
name='favicon_file',
|
name="favicon_file",
|
||||||
field=models.CharField(blank=True, max_length=512),
|
field=models.CharField(blank=True, max_length=512),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,13 +6,13 @@ from django.db import migrations, models
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('bookmarks', '0018_bookmark_favicon_file'),
|
("bookmarks", "0018_bookmark_favicon_file"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='userprofile',
|
model_name="userprofile",
|
||||||
name='enable_favicons',
|
name="enable_favicons",
|
||||||
field=models.BooleanField(default=False),
|
field=models.BooleanField(default=False),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,13 +6,17 @@ from django.db import migrations, models
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('bookmarks', '0019_userprofile_enable_favicons'),
|
("bookmarks", "0019_userprofile_enable_favicons"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='userprofile',
|
model_name="userprofile",
|
||||||
name='tag_search',
|
name="tag_search",
|
||||||
field=models.CharField(choices=[('strict', 'Strict'), ('lax', 'Lax')], default='strict', max_length=10),
|
field=models.CharField(
|
||||||
|
choices=[("strict", "Strict"), ("lax", "Lax")],
|
||||||
|
default="strict",
|
||||||
|
max_length=10,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,13 +6,13 @@ from django.db import migrations, models
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('bookmarks', '0020_userprofile_tag_search'),
|
("bookmarks", "0020_userprofile_tag_search"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='userprofile',
|
model_name="userprofile",
|
||||||
name='display_url',
|
name="display_url",
|
||||||
field=models.BooleanField(default=False),
|
field=models.BooleanField(default=False),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,13 +6,13 @@ from django.db import migrations, models
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('bookmarks', '0021_userprofile_display_url'),
|
("bookmarks", "0021_userprofile_display_url"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='bookmark',
|
model_name="bookmark",
|
||||||
name='notes',
|
name="notes",
|
||||||
field=models.TextField(blank=True),
|
field=models.TextField(blank=True),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,13 +6,13 @@ from django.db import migrations, models
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('bookmarks', '0022_bookmark_notes'),
|
("bookmarks", "0022_bookmark_notes"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='userprofile',
|
model_name="userprofile",
|
||||||
name='permanent_notes',
|
name="permanent_notes",
|
||||||
field=models.BooleanField(default=False),
|
field=models.BooleanField(default=False),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,13 +6,13 @@ from django.db import migrations, models
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('bookmarks', '0023_userprofile_permanent_notes'),
|
("bookmarks", "0023_userprofile_permanent_notes"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='userprofile',
|
model_name="userprofile",
|
||||||
name='enable_public_sharing',
|
name="enable_public_sharing",
|
||||||
field=models.BooleanField(default=False),
|
field=models.BooleanField(default=False),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,13 +6,13 @@ from django.db import migrations, models
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('bookmarks', '0024_userprofile_enable_public_sharing'),
|
("bookmarks", "0024_userprofile_enable_public_sharing"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='userprofile',
|
model_name="userprofile",
|
||||||
name='search_preferences',
|
name="search_preferences",
|
||||||
field=models.JSONField(default=dict),
|
field=models.JSONField(default=dict),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -26,10 +26,10 @@ class Tag(models.Model):
|
||||||
def sanitize_tag_name(tag_name: str):
|
def sanitize_tag_name(tag_name: str):
|
||||||
# strip leading/trailing spaces
|
# strip leading/trailing spaces
|
||||||
# replace inner spaces with replacement char
|
# 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:
|
if not tag_string:
|
||||||
return []
|
return []
|
||||||
names = tag_string.strip().split(delimiter)
|
names = tag_string.strip().split(delimiter)
|
||||||
|
@ -42,7 +42,7 @@ def parse_tag_string(tag_string: str, delimiter: str = ','):
|
||||||
return names
|
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)
|
return delimiter.join(tag_names)
|
||||||
|
|
||||||
|
|
||||||
|
@ -82,7 +82,7 @@ class Bookmark(models.Model):
|
||||||
return [tag.name for tag in self.tags.all()]
|
return [tag.name for tag in self.tags.all()]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.resolved_title + ' (' + self.url[:30] + '...)'
|
return self.resolved_title + " (" + self.url[:30] + "...)"
|
||||||
|
|
||||||
|
|
||||||
class BookmarkForm(forms.ModelForm):
|
class BookmarkForm(forms.ModelForm):
|
||||||
|
@ -90,15 +90,13 @@ class BookmarkForm(forms.ModelForm):
|
||||||
url = forms.CharField(validators=[BookmarkURLValidator()])
|
url = forms.CharField(validators=[BookmarkURLValidator()])
|
||||||
tag_string = forms.CharField(required=False)
|
tag_string = forms.CharField(required=False)
|
||||||
# Do not require title and description in form as we fill these automatically if they are empty
|
# Do not require title and description in form as we fill these automatically if they are empty
|
||||||
title = forms.CharField(max_length=512,
|
title = forms.CharField(max_length=512, required=False)
|
||||||
required=False)
|
description = forms.CharField(required=False, widget=forms.Textarea())
|
||||||
description = forms.CharField(required=False,
|
|
||||||
widget=forms.Textarea())
|
|
||||||
# Include website title and description as hidden field as they only provide info when editing bookmarks
|
# Include website title and description as hidden field as they only provide info when editing bookmarks
|
||||||
website_title = forms.CharField(max_length=512,
|
website_title = forms.CharField(
|
||||||
required=False, widget=forms.HiddenInput())
|
max_length=512, required=False, widget=forms.HiddenInput()
|
||||||
website_description = forms.CharField(required=False,
|
)
|
||||||
widget=forms.HiddenInput())
|
website_description = forms.CharField(required=False, widget=forms.HiddenInput())
|
||||||
unread = forms.BooleanField(required=False)
|
unread = forms.BooleanField(required=False)
|
||||||
shared = forms.BooleanField(required=False)
|
shared = forms.BooleanField(required=False)
|
||||||
# Hidden field that determines whether to close window/tab after saving the bookmark
|
# Hidden field that determines whether to close window/tab after saving the bookmark
|
||||||
|
@ -107,16 +105,16 @@ class BookmarkForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Bookmark
|
model = Bookmark
|
||||||
fields = [
|
fields = [
|
||||||
'url',
|
"url",
|
||||||
'tag_string',
|
"tag_string",
|
||||||
'title',
|
"title",
|
||||||
'description',
|
"description",
|
||||||
'notes',
|
"notes",
|
||||||
'website_title',
|
"website_title",
|
||||||
'website_description',
|
"website_description",
|
||||||
'unread',
|
"unread",
|
||||||
'shared',
|
"shared",
|
||||||
'auto_close',
|
"auto_close",
|
||||||
]
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -125,45 +123,47 @@ class BookmarkForm(forms.ModelForm):
|
||||||
|
|
||||||
|
|
||||||
class BookmarkSearch:
|
class BookmarkSearch:
|
||||||
SORT_ADDED_ASC = 'added_asc'
|
SORT_ADDED_ASC = "added_asc"
|
||||||
SORT_ADDED_DESC = 'added_desc'
|
SORT_ADDED_DESC = "added_desc"
|
||||||
SORT_TITLE_ASC = 'title_asc'
|
SORT_TITLE_ASC = "title_asc"
|
||||||
SORT_TITLE_DESC = 'title_desc'
|
SORT_TITLE_DESC = "title_desc"
|
||||||
|
|
||||||
FILTER_SHARED_OFF = 'off'
|
FILTER_SHARED_OFF = "off"
|
||||||
FILTER_SHARED_SHARED = 'yes'
|
FILTER_SHARED_SHARED = "yes"
|
||||||
FILTER_SHARED_UNSHARED = 'no'
|
FILTER_SHARED_UNSHARED = "no"
|
||||||
|
|
||||||
FILTER_UNREAD_OFF = 'off'
|
FILTER_UNREAD_OFF = "off"
|
||||||
FILTER_UNREAD_YES = 'yes'
|
FILTER_UNREAD_YES = "yes"
|
||||||
FILTER_UNREAD_NO = 'no'
|
FILTER_UNREAD_NO = "no"
|
||||||
|
|
||||||
params = ['q', 'user', 'sort', 'shared', 'unread']
|
params = ["q", "user", "sort", "shared", "unread"]
|
||||||
preferences = ['sort', 'shared', 'unread']
|
preferences = ["sort", "shared", "unread"]
|
||||||
defaults = {
|
defaults = {
|
||||||
'q': '',
|
"q": "",
|
||||||
'user': '',
|
"user": "",
|
||||||
'sort': SORT_ADDED_DESC,
|
"sort": SORT_ADDED_DESC,
|
||||||
'shared': FILTER_SHARED_OFF,
|
"shared": FILTER_SHARED_OFF,
|
||||||
'unread': FILTER_UNREAD_OFF,
|
"unread": FILTER_UNREAD_OFF,
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(
|
||||||
|
self,
|
||||||
q: str = None,
|
q: str = None,
|
||||||
user: str = None,
|
user: str = None,
|
||||||
sort: str = None,
|
sort: str = None,
|
||||||
shared: str = None,
|
shared: str = None,
|
||||||
unread: str = None,
|
unread: str = None,
|
||||||
preferences: dict = None):
|
preferences: dict = None,
|
||||||
|
):
|
||||||
if not preferences:
|
if not preferences:
|
||||||
preferences = {}
|
preferences = {}
|
||||||
self.defaults = {**BookmarkSearch.defaults, **preferences}
|
self.defaults = {**BookmarkSearch.defaults, **preferences}
|
||||||
|
|
||||||
self.q = q or self.defaults['q']
|
self.q = q or self.defaults["q"]
|
||||||
self.user = user or self.defaults['user']
|
self.user = user or self.defaults["user"]
|
||||||
self.sort = sort or self.defaults['sort']
|
self.sort = sort or self.defaults["sort"]
|
||||||
self.shared = shared or self.defaults['shared']
|
self.shared = shared or self.defaults["shared"]
|
||||||
self.unread = unread or self.defaults['unread']
|
self.unread = unread or self.defaults["unread"]
|
||||||
|
|
||||||
def is_modified(self, param):
|
def is_modified(self, param):
|
||||||
value = self.__dict__[param]
|
value = self.__dict__[param]
|
||||||
|
@ -175,7 +175,11 @@ class BookmarkSearch:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def modified_preferences(self):
|
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
|
@property
|
||||||
def has_modifications(self):
|
def has_modifications(self):
|
||||||
|
@ -191,7 +195,9 @@ class BookmarkSearch:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def preferences_dict(self):
|
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
|
@staticmethod
|
||||||
def from_request(query_dict: QueryDict, preferences: dict = None):
|
def from_request(query_dict: QueryDict, preferences: dict = None):
|
||||||
|
@ -206,20 +212,20 @@ class BookmarkSearch:
|
||||||
|
|
||||||
class BookmarkSearchForm(forms.Form):
|
class BookmarkSearchForm(forms.Form):
|
||||||
SORT_CHOICES = [
|
SORT_CHOICES = [
|
||||||
(BookmarkSearch.SORT_ADDED_ASC, 'Added ↑'),
|
(BookmarkSearch.SORT_ADDED_ASC, "Added ↑"),
|
||||||
(BookmarkSearch.SORT_ADDED_DESC, 'Added ↓'),
|
(BookmarkSearch.SORT_ADDED_DESC, "Added ↓"),
|
||||||
(BookmarkSearch.SORT_TITLE_ASC, 'Title ↑'),
|
(BookmarkSearch.SORT_TITLE_ASC, "Title ↑"),
|
||||||
(BookmarkSearch.SORT_TITLE_DESC, 'Title ↓'),
|
(BookmarkSearch.SORT_TITLE_DESC, "Title ↓"),
|
||||||
]
|
]
|
||||||
FILTER_SHARED_CHOICES = [
|
FILTER_SHARED_CHOICES = [
|
||||||
(BookmarkSearch.FILTER_SHARED_OFF, 'Off'),
|
(BookmarkSearch.FILTER_SHARED_OFF, "Off"),
|
||||||
(BookmarkSearch.FILTER_SHARED_SHARED, 'Shared'),
|
(BookmarkSearch.FILTER_SHARED_SHARED, "Shared"),
|
||||||
(BookmarkSearch.FILTER_SHARED_UNSHARED, 'Unshared'),
|
(BookmarkSearch.FILTER_SHARED_UNSHARED, "Unshared"),
|
||||||
]
|
]
|
||||||
FILTER_UNREAD_CHOICES = [
|
FILTER_UNREAD_CHOICES = [
|
||||||
(BookmarkSearch.FILTER_UNREAD_OFF, 'Off'),
|
(BookmarkSearch.FILTER_UNREAD_OFF, "Off"),
|
||||||
(BookmarkSearch.FILTER_UNREAD_YES, 'Unread'),
|
(BookmarkSearch.FILTER_UNREAD_YES, "Unread"),
|
||||||
(BookmarkSearch.FILTER_UNREAD_NO, 'Read'),
|
(BookmarkSearch.FILTER_UNREAD_NO, "Read"),
|
||||||
]
|
]
|
||||||
|
|
||||||
q = forms.CharField()
|
q = forms.CharField()
|
||||||
|
@ -228,7 +234,12 @@ class BookmarkSearchForm(forms.Form):
|
||||||
shared = forms.ChoiceField(choices=FILTER_SHARED_CHOICES, widget=forms.RadioSelect)
|
shared = forms.ChoiceField(choices=FILTER_SHARED_CHOICES, widget=forms.RadioSelect)
|
||||||
unread = forms.ChoiceField(choices=FILTER_UNREAD_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__()
|
super().__init__()
|
||||||
editable_fields = editable_fields or []
|
editable_fields = editable_fields or []
|
||||||
self.editable_fields = editable_fields
|
self.editable_fields = editable_fields
|
||||||
|
@ -236,8 +247,8 @@ class BookmarkSearchForm(forms.Form):
|
||||||
# set choices for user field if users are provided
|
# set choices for user field if users are provided
|
||||||
if users:
|
if users:
|
||||||
user_choices = [(user.username, user.username) for user in users]
|
user_choices = [(user.username, user.username) for user in users]
|
||||||
user_choices.insert(0, ('', 'Everyone'))
|
user_choices.insert(0, ("", "Everyone"))
|
||||||
self.fields['user'].choices = user_choices
|
self.fields["user"].choices = user_choices
|
||||||
|
|
||||||
for param in search.params:
|
for param in search.params:
|
||||||
# set initial values for modified params
|
# set initial values for modified params
|
||||||
|
@ -251,50 +262,70 @@ class BookmarkSearchForm(forms.Form):
|
||||||
|
|
||||||
|
|
||||||
class UserProfile(models.Model):
|
class UserProfile(models.Model):
|
||||||
THEME_AUTO = 'auto'
|
THEME_AUTO = "auto"
|
||||||
THEME_LIGHT = 'light'
|
THEME_LIGHT = "light"
|
||||||
THEME_DARK = 'dark'
|
THEME_DARK = "dark"
|
||||||
THEME_CHOICES = [
|
THEME_CHOICES = [
|
||||||
(THEME_AUTO, 'Auto'),
|
(THEME_AUTO, "Auto"),
|
||||||
(THEME_LIGHT, 'Light'),
|
(THEME_LIGHT, "Light"),
|
||||||
(THEME_DARK, 'Dark'),
|
(THEME_DARK, "Dark"),
|
||||||
]
|
]
|
||||||
BOOKMARK_DATE_DISPLAY_RELATIVE = 'relative'
|
BOOKMARK_DATE_DISPLAY_RELATIVE = "relative"
|
||||||
BOOKMARK_DATE_DISPLAY_ABSOLUTE = 'absolute'
|
BOOKMARK_DATE_DISPLAY_ABSOLUTE = "absolute"
|
||||||
BOOKMARK_DATE_DISPLAY_HIDDEN = 'hidden'
|
BOOKMARK_DATE_DISPLAY_HIDDEN = "hidden"
|
||||||
BOOKMARK_DATE_DISPLAY_CHOICES = [
|
BOOKMARK_DATE_DISPLAY_CHOICES = [
|
||||||
(BOOKMARK_DATE_DISPLAY_RELATIVE, 'Relative'),
|
(BOOKMARK_DATE_DISPLAY_RELATIVE, "Relative"),
|
||||||
(BOOKMARK_DATE_DISPLAY_ABSOLUTE, 'Absolute'),
|
(BOOKMARK_DATE_DISPLAY_ABSOLUTE, "Absolute"),
|
||||||
(BOOKMARK_DATE_DISPLAY_HIDDEN, 'Hidden'),
|
(BOOKMARK_DATE_DISPLAY_HIDDEN, "Hidden"),
|
||||||
]
|
]
|
||||||
BOOKMARK_LINK_TARGET_BLANK = '_blank'
|
BOOKMARK_LINK_TARGET_BLANK = "_blank"
|
||||||
BOOKMARK_LINK_TARGET_SELF = '_self'
|
BOOKMARK_LINK_TARGET_SELF = "_self"
|
||||||
BOOKMARK_LINK_TARGET_CHOICES = [
|
BOOKMARK_LINK_TARGET_CHOICES = [
|
||||||
(BOOKMARK_LINK_TARGET_BLANK, 'New page'),
|
(BOOKMARK_LINK_TARGET_BLANK, "New page"),
|
||||||
(BOOKMARK_LINK_TARGET_SELF, 'Same page'),
|
(BOOKMARK_LINK_TARGET_SELF, "Same page"),
|
||||||
]
|
]
|
||||||
WEB_ARCHIVE_INTEGRATION_DISABLED = 'disabled'
|
WEB_ARCHIVE_INTEGRATION_DISABLED = "disabled"
|
||||||
WEB_ARCHIVE_INTEGRATION_ENABLED = 'enabled'
|
WEB_ARCHIVE_INTEGRATION_ENABLED = "enabled"
|
||||||
WEB_ARCHIVE_INTEGRATION_CHOICES = [
|
WEB_ARCHIVE_INTEGRATION_CHOICES = [
|
||||||
(WEB_ARCHIVE_INTEGRATION_DISABLED, 'Disabled'),
|
(WEB_ARCHIVE_INTEGRATION_DISABLED, "Disabled"),
|
||||||
(WEB_ARCHIVE_INTEGRATION_ENABLED, 'Enabled'),
|
(WEB_ARCHIVE_INTEGRATION_ENABLED, "Enabled"),
|
||||||
]
|
]
|
||||||
TAG_SEARCH_STRICT = 'strict'
|
TAG_SEARCH_STRICT = "strict"
|
||||||
TAG_SEARCH_LAX = 'lax'
|
TAG_SEARCH_LAX = "lax"
|
||||||
TAG_SEARCH_CHOICES = [
|
TAG_SEARCH_CHOICES = [
|
||||||
(TAG_SEARCH_STRICT, 'Strict'),
|
(TAG_SEARCH_STRICT, "Strict"),
|
||||||
(TAG_SEARCH_LAX, 'Lax'),
|
(TAG_SEARCH_LAX, "Lax"),
|
||||||
]
|
]
|
||||||
user = models.OneToOneField(get_user_model(), related_name='profile', on_delete=models.CASCADE)
|
user = models.OneToOneField(
|
||||||
theme = models.CharField(max_length=10, choices=THEME_CHOICES, blank=False, default=THEME_AUTO)
|
get_user_model(), related_name="profile", on_delete=models.CASCADE
|
||||||
bookmark_date_display = models.CharField(max_length=10, choices=BOOKMARK_DATE_DISPLAY_CHOICES, blank=False,
|
)
|
||||||
default=BOOKMARK_DATE_DISPLAY_RELATIVE)
|
theme = models.CharField(
|
||||||
bookmark_link_target = models.CharField(max_length=10, choices=BOOKMARK_LINK_TARGET_CHOICES, blank=False,
|
max_length=10, choices=THEME_CHOICES, blank=False, default=THEME_AUTO
|
||||||
default=BOOKMARK_LINK_TARGET_BLANK)
|
)
|
||||||
web_archive_integration = models.CharField(max_length=10, choices=WEB_ARCHIVE_INTEGRATION_CHOICES, blank=False,
|
bookmark_date_display = models.CharField(
|
||||||
default=WEB_ARCHIVE_INTEGRATION_DISABLED)
|
max_length=10,
|
||||||
tag_search = models.CharField(max_length=10, choices=TAG_SEARCH_CHOICES, blank=False,
|
choices=BOOKMARK_DATE_DISPLAY_CHOICES,
|
||||||
default=TAG_SEARCH_STRICT)
|
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_sharing = models.BooleanField(default=False, null=False)
|
||||||
enable_public_sharing = models.BooleanField(default=False, null=False)
|
enable_public_sharing = models.BooleanField(default=False, null=False)
|
||||||
enable_favicons = models.BooleanField(default=False, null=False)
|
enable_favicons = models.BooleanField(default=False, null=False)
|
||||||
|
@ -306,8 +337,18 @@ class UserProfile(models.Model):
|
||||||
class UserProfileForm(forms.ModelForm):
|
class UserProfileForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = UserProfile
|
model = UserProfile
|
||||||
fields = ['theme', 'bookmark_date_display', 'bookmark_link_target', 'web_archive_integration', 'tag_search',
|
fields = [
|
||||||
'enable_sharing', 'enable_public_sharing', 'enable_favicons', 'display_url', 'permanent_notes']
|
"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())
|
@receiver(post_save, sender=get_user_model())
|
||||||
|
@ -332,9 +373,11 @@ class FeedToken(models.Model):
|
||||||
"""
|
"""
|
||||||
Adapted from authtoken.models.Token
|
Adapted from authtoken.models.Token
|
||||||
"""
|
"""
|
||||||
|
|
||||||
key = models.CharField(max_length=40, primary_key=True)
|
key = models.CharField(max_length=40, primary_key=True)
|
||||||
user = models.OneToOneField(get_user_model(),
|
user = models.OneToOneField(
|
||||||
related_name='feed_token',
|
get_user_model(),
|
||||||
|
related_name="feed_token",
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
)
|
)
|
||||||
created = models.DateTimeField(auto_now_add=True)
|
created = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
|
@ -10,18 +10,24 @@ from bookmarks.models import Bookmark, BookmarkSearch, Tag, UserProfile
|
||||||
from bookmarks.utils import unique
|
from bookmarks.utils import unique
|
||||||
|
|
||||||
|
|
||||||
def query_bookmarks(user: User, profile: UserProfile, search: BookmarkSearch) -> QuerySet:
|
def query_bookmarks(
|
||||||
return _base_bookmarks_query(user, profile, search) \
|
user: User, profile: UserProfile, search: BookmarkSearch
|
||||||
.filter(is_archived=False)
|
) -> QuerySet:
|
||||||
|
return _base_bookmarks_query(user, profile, search).filter(is_archived=False)
|
||||||
|
|
||||||
|
|
||||||
def query_archived_bookmarks(user: User, profile: UserProfile, search: BookmarkSearch) -> QuerySet:
|
def query_archived_bookmarks(
|
||||||
return _base_bookmarks_query(user, profile, search) \
|
user: User, profile: UserProfile, search: BookmarkSearch
|
||||||
.filter(is_archived=True)
|
) -> QuerySet:
|
||||||
|
return _base_bookmarks_query(user, profile, search).filter(is_archived=True)
|
||||||
|
|
||||||
|
|
||||||
def query_shared_bookmarks(user: Optional[User], profile: UserProfile, search: BookmarkSearch,
|
def query_shared_bookmarks(
|
||||||
public_only: bool) -> QuerySet:
|
user: Optional[User],
|
||||||
|
profile: UserProfile,
|
||||||
|
search: BookmarkSearch,
|
||||||
|
public_only: bool,
|
||||||
|
) -> QuerySet:
|
||||||
conditions = Q(shared=True) & Q(owner__profile__enable_sharing=True)
|
conditions = Q(shared=True) & Q(owner__profile__enable_sharing=True)
|
||||||
if public_only:
|
if public_only:
|
||||||
conditions = conditions & Q(owner__profile__enable_public_sharing=True)
|
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)
|
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
|
query_set = Bookmark.objects
|
||||||
|
|
||||||
# Filter for user
|
# Filter for user
|
||||||
|
@ -40,34 +48,32 @@ def _base_bookmarks_query(user: Optional[User], profile: UserProfile, search: Bo
|
||||||
query = parse_query_string(search.q)
|
query = parse_query_string(search.q)
|
||||||
|
|
||||||
# Filter for search terms and tags
|
# Filter for search terms and tags
|
||||||
for term in query['search_terms']:
|
for term in query["search_terms"]:
|
||||||
conditions = Q(title__icontains=term) \
|
conditions = (
|
||||||
| Q(description__icontains=term) \
|
Q(title__icontains=term)
|
||||||
| Q(notes__icontains=term) \
|
| Q(description__icontains=term)
|
||||||
| Q(website_title__icontains=term) \
|
| Q(notes__icontains=term)
|
||||||
| Q(website_description__icontains=term) \
|
| Q(website_title__icontains=term)
|
||||||
|
| Q(website_description__icontains=term)
|
||||||
| Q(url__icontains=term)
|
| Q(url__icontains=term)
|
||||||
|
)
|
||||||
|
|
||||||
if profile.tag_search == UserProfile.TAG_SEARCH_LAX:
|
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)
|
query_set = query_set.filter(conditions)
|
||||||
|
|
||||||
for tag_name in query['tag_names']:
|
for tag_name in query["tag_names"]:
|
||||||
query_set = query_set.filter(
|
query_set = query_set.filter(tags__name__iexact=tag_name)
|
||||||
tags__name__iexact=tag_name
|
|
||||||
)
|
|
||||||
|
|
||||||
# Untagged bookmarks
|
# Untagged bookmarks
|
||||||
if query['untagged']:
|
if query["untagged"]:
|
||||||
query_set = query_set.filter(
|
query_set = query_set.filter(tags=None)
|
||||||
tags=None
|
|
||||||
)
|
|
||||||
# Legacy unread bookmarks filter from query
|
# Legacy unread bookmarks filter from query
|
||||||
if query['unread']:
|
if query["unread"]:
|
||||||
query_set = query_set.filter(
|
query_set = query_set.filter(unread=True)
|
||||||
unread=True
|
|
||||||
)
|
|
||||||
|
|
||||||
# Unread filter from bookmark search
|
# Unread filter from bookmark search
|
||||||
if search.unread == BookmarkSearch.FILTER_UNREAD_YES:
|
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
|
# Sort by date added
|
||||||
if search.sort == BookmarkSearch.SORT_ADDED_ASC:
|
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:
|
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
|
# 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
|
# For the title, the resolved_title logic from the Bookmark entity needs
|
||||||
# to be replicated as there is no corresponding database field
|
# to be replicated as there is no corresponding database field
|
||||||
query_set = query_set.annotate(
|
query_set = query_set.annotate(
|
||||||
effective_title=Case(
|
effective_title=Case(
|
||||||
When(Q(title__isnull=False) & ~Q(title__exact=''), then=Lower('title')),
|
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')),
|
When(
|
||||||
default=Lower('url'),
|
Q(website_title__isnull=False) & ~Q(website_title__exact=""),
|
||||||
output_field=CharField()
|
then=Lower("website_title"),
|
||||||
))
|
),
|
||||||
|
default=Lower("url"),
|
||||||
|
output_field=CharField(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# For SQLite, if the ICU extension is loaded, use the custom collation
|
# For SQLite, if the ICU extension is loaded, use the custom collation
|
||||||
# loaded into the connection. This results in an improved sort order for
|
# loaded into the connection. This results in an improved sort order for
|
||||||
# unicode characters (umlauts, etc.)
|
# unicode characters (umlauts, etc.)
|
||||||
if settings.USE_SQLITE and settings.USE_SQLITE_ICU_EXTENSION:
|
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:
|
else:
|
||||||
order_field = 'effective_title'
|
order_field = "effective_title"
|
||||||
|
|
||||||
if search.sort == BookmarkSearch.SORT_TITLE_ASC:
|
if search.sort == BookmarkSearch.SORT_TITLE_ASC:
|
||||||
query_set = query_set.order_by(order_field)
|
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
|
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)
|
bookmarks_query = query_bookmarks(user, profile, search)
|
||||||
|
|
||||||
query_set = Tag.objects.filter(bookmark__in=bookmarks_query)
|
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()
|
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)
|
bookmarks_query = query_archived_bookmarks(user, profile, search)
|
||||||
|
|
||||||
query_set = Tag.objects.filter(bookmark__in=bookmarks_query)
|
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()
|
return query_set.distinct()
|
||||||
|
|
||||||
|
|
||||||
def query_shared_bookmark_tags(user: Optional[User], profile: UserProfile, search: BookmarkSearch,
|
def query_shared_bookmark_tags(
|
||||||
public_only: bool) -> QuerySet:
|
user: Optional[User],
|
||||||
|
profile: UserProfile,
|
||||||
|
search: BookmarkSearch,
|
||||||
|
public_only: bool,
|
||||||
|
) -> QuerySet:
|
||||||
bookmarks_query = query_shared_bookmarks(user, profile, search, public_only)
|
bookmarks_query = query_shared_bookmarks(user, profile, search, public_only)
|
||||||
|
|
||||||
query_set = Tag.objects.filter(bookmark__in=bookmarks_query)
|
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()
|
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)
|
bookmarks_query = query_shared_bookmarks(None, profile, search, public_only)
|
||||||
|
|
||||||
query_set = User.objects.filter(bookmark__in=bookmarks_query)
|
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):
|
def parse_query_string(query_string):
|
||||||
# Sanitize query params
|
# Sanitize query params
|
||||||
if not query_string:
|
if not query_string:
|
||||||
query_string = ''
|
query_string = ""
|
||||||
|
|
||||||
# Split query into search terms and tags
|
# 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]
|
keywords = [word for word in keywords if word]
|
||||||
|
|
||||||
search_terms = [word for word in keywords if word[0] != '#' and 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 = [word[1:] for word in keywords if word[0] == "#"]
|
||||||
tag_names = unique(tag_names, str.lower)
|
tag_names = unique(tag_names, str.lower)
|
||||||
|
|
||||||
# Special search commands
|
# Special search commands
|
||||||
untagged = '!untagged' in keywords
|
untagged = "!untagged" in keywords
|
||||||
unread = '!unread' in keywords
|
unread = "!unread" in keywords
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'search_terms': search_terms,
|
"search_terms": search_terms,
|
||||||
'tag_names': tag_names,
|
"tag_names": tag_names,
|
||||||
'untagged': untagged,
|
"untagged": untagged,
|
||||||
'unread': unread,
|
"unread": unread,
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,9 @@ from bookmarks.services import tasks
|
||||||
|
|
||||||
def create_bookmark(bookmark: Bookmark, tag_string: str, current_user: User):
|
def create_bookmark(bookmark: Bookmark, tag_string: str, current_user: User):
|
||||||
# If URL is already bookmarked, then update it
|
# 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:
|
if existing_bookmark is not None:
|
||||||
_merge_bookmark_data(bookmark, existing_bookmark)
|
_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):
|
def archive_bookmarks(bookmark_ids: [Union[int, str]], current_user: User):
|
||||||
sanitized_bookmark_ids = _sanitize_id_list(bookmark_ids)
|
sanitized_bookmark_ids = _sanitize_id_list(bookmark_ids)
|
||||||
|
|
||||||
Bookmark.objects.filter(owner=current_user, id__in=sanitized_bookmark_ids).update(is_archived=True,
|
Bookmark.objects.filter(owner=current_user, id__in=sanitized_bookmark_ids).update(
|
||||||
date_modified=timezone.now())
|
is_archived=True, date_modified=timezone.now()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def unarchive_bookmark(bookmark: Bookmark):
|
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):
|
def unarchive_bookmarks(bookmark_ids: [Union[int, str]], current_user: User):
|
||||||
sanitized_bookmark_ids = _sanitize_id_list(bookmark_ids)
|
sanitized_bookmark_ids = _sanitize_id_list(bookmark_ids)
|
||||||
|
|
||||||
Bookmark.objects.filter(owner=current_user, id__in=sanitized_bookmark_ids).update(is_archived=False,
|
Bookmark.objects.filter(owner=current_user, id__in=sanitized_bookmark_ids).update(
|
||||||
date_modified=timezone.now())
|
is_archived=False, date_modified=timezone.now()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def delete_bookmarks(bookmark_ids: [Union[int, str]], current_user: User):
|
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):
|
def tag_bookmarks(bookmark_ids: [Union[int, str]], tag_string: str, current_user: User):
|
||||||
sanitized_bookmark_ids = _sanitize_id_list(bookmark_ids)
|
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',
|
owned_bookmark_ids = Bookmark.objects.filter(
|
||||||
flat=True)
|
owner=current_user, id__in=sanitized_bookmark_ids
|
||||||
|
).values_list("id", flat=True)
|
||||||
tag_names = parse_tag_string(tag_string)
|
tag_names = parse_tag_string(tag_string)
|
||||||
tags = get_or_create_tags(tag_names, current_user)
|
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 = []
|
relationships = []
|
||||||
for tag in tags:
|
for tag in tags:
|
||||||
for bookmark_id in owned_bookmark_ids:
|
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
|
# Insert all bookmark -> tag associations at once, should ignore errors if association already exists
|
||||||
BookmarkToTagRelationShip.objects.bulk_create(relationships, ignore_conflicts=True)
|
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)
|
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',
|
owned_bookmark_ids = Bookmark.objects.filter(
|
||||||
flat=True)
|
owner=current_user, id__in=sanitized_bookmark_ids
|
||||||
|
).values_list("id", flat=True)
|
||||||
tag_names = parse_tag_string(tag_string)
|
tag_names = parse_tag_string(tag_string)
|
||||||
tags = get_or_create_tags(tag_names, current_user)
|
tags = get_or_create_tags(tag_names, current_user)
|
||||||
|
|
||||||
BookmarkToTagRelationShip = Bookmark.tags.through
|
BookmarkToTagRelationShip = Bookmark.tags.through
|
||||||
for tag in tags:
|
for tag in tags:
|
||||||
# Remove all bookmark -> tag associations for the owned bookmarks and the current tag
|
# 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):
|
def mark_bookmarks_as_read(bookmark_ids: [Union[int, str]], current_user: User):
|
||||||
sanitized_bookmark_ids = _sanitize_id_list(bookmark_ids)
|
sanitized_bookmark_ids = _sanitize_id_list(bookmark_ids)
|
||||||
|
|
||||||
Bookmark.objects.filter(owner=current_user, id__in=sanitized_bookmark_ids).update(unread=False,
|
Bookmark.objects.filter(owner=current_user, id__in=sanitized_bookmark_ids).update(
|
||||||
date_modified=timezone.now())
|
unread=False, date_modified=timezone.now()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def mark_bookmarks_as_unread(bookmark_ids: [Union[int, str]], current_user: User):
|
def mark_bookmarks_as_unread(bookmark_ids: [Union[int, str]], current_user: User):
|
||||||
sanitized_bookmark_ids = _sanitize_id_list(bookmark_ids)
|
sanitized_bookmark_ids = _sanitize_id_list(bookmark_ids)
|
||||||
|
|
||||||
Bookmark.objects.filter(owner=current_user, id__in=sanitized_bookmark_ids).update(unread=True,
|
Bookmark.objects.filter(owner=current_user, id__in=sanitized_bookmark_ids).update(
|
||||||
date_modified=timezone.now())
|
unread=True, date_modified=timezone.now()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def share_bookmarks(bookmark_ids: [Union[int, str]], current_user: User):
|
def share_bookmarks(bookmark_ids: [Union[int, str]], current_user: User):
|
||||||
sanitized_bookmark_ids = _sanitize_id_list(bookmark_ids)
|
sanitized_bookmark_ids = _sanitize_id_list(bookmark_ids)
|
||||||
|
|
||||||
Bookmark.objects.filter(owner=current_user, id__in=sanitized_bookmark_ids).update(shared=True,
|
Bookmark.objects.filter(owner=current_user, id__in=sanitized_bookmark_ids).update(
|
||||||
date_modified=timezone.now())
|
shared=True, date_modified=timezone.now()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def unshare_bookmarks(bookmark_ids: [Union[int, str]], current_user: User):
|
def unshare_bookmarks(bookmark_ids: [Union[int, str]], current_user: User):
|
||||||
sanitized_bookmark_ids = _sanitize_id_list(bookmark_ids)
|
sanitized_bookmark_ids = _sanitize_id_list(bookmark_ids)
|
||||||
|
|
||||||
Bookmark.objects.filter(owner=current_user, id__in=sanitized_bookmark_ids).update(shared=False,
|
Bookmark.objects.filter(owner=current_user, id__in=sanitized_bookmark_ids).update(
|
||||||
date_modified=timezone.now())
|
shared=False, date_modified=timezone.now()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _merge_bookmark_data(from_bookmark: Bookmark, to_bookmark: Bookmark):
|
def _merge_bookmark_data(from_bookmark: Bookmark, to_bookmark: Bookmark):
|
||||||
|
|
|
@ -13,40 +13,41 @@ def export_netscape_html(bookmarks: List[Bookmark]):
|
||||||
[append_bookmark(doc, bookmark) for bookmark in bookmarks]
|
[append_bookmark(doc, bookmark) for bookmark in bookmarks]
|
||||||
append_list_end(doc)
|
append_list_end(doc)
|
||||||
|
|
||||||
return '\n\r'.join(doc)
|
return "\n\r".join(doc)
|
||||||
|
|
||||||
|
|
||||||
def append_header(doc: BookmarkDocument):
|
def append_header(doc: BookmarkDocument):
|
||||||
doc.append('<!DOCTYPE NETSCAPE-Bookmark-file-1>')
|
doc.append("<!DOCTYPE NETSCAPE-Bookmark-file-1>")
|
||||||
doc.append('<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">')
|
doc.append('<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">')
|
||||||
doc.append('<TITLE>Bookmarks</TITLE>')
|
doc.append("<TITLE>Bookmarks</TITLE>")
|
||||||
doc.append('<H1>Bookmarks</H1>')
|
doc.append("<H1>Bookmarks</H1>")
|
||||||
|
|
||||||
|
|
||||||
def append_list_start(doc: BookmarkDocument):
|
def append_list_start(doc: BookmarkDocument):
|
||||||
doc.append('<DL><p>')
|
doc.append("<DL><p>")
|
||||||
|
|
||||||
|
|
||||||
def append_bookmark(doc: BookmarkDocument, bookmark: Bookmark):
|
def append_bookmark(doc: BookmarkDocument, bookmark: Bookmark):
|
||||||
url = bookmark.url
|
url = bookmark.url
|
||||||
title = html.escape(bookmark.resolved_title or '')
|
title = html.escape(bookmark.resolved_title or "")
|
||||||
desc = html.escape(bookmark.resolved_description or '')
|
desc = html.escape(bookmark.resolved_description or "")
|
||||||
if bookmark.notes:
|
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
|
tag_names = bookmark.tag_names
|
||||||
if bookmark.is_archived:
|
if bookmark.is_archived:
|
||||||
tag_names.append('linkding:archived')
|
tag_names.append("linkding:archived")
|
||||||
tags = ','.join(tag_names)
|
tags = ",".join(tag_names)
|
||||||
toread = '1' if bookmark.unread else '0'
|
toread = "1" if bookmark.unread else "0"
|
||||||
private = '0' if bookmark.shared else '1'
|
private = "0" if bookmark.shared else "1"
|
||||||
added = int(bookmark.date_added.timestamp())
|
added = int(bookmark.date_added.timestamp())
|
||||||
|
|
||||||
doc.append(
|
doc.append(
|
||||||
f'<DT><A HREF="{url}" ADD_DATE="{added}" PRIVATE="{private}" TOREAD="{toread}" TAGS="{tags}">{title}</A>')
|
f'<DT><A HREF="{url}" ADD_DATE="{added}" PRIVATE="{private}" TOREAD="{toread}" TAGS="{tags}">{title}</A>'
|
||||||
|
)
|
||||||
|
|
||||||
if desc:
|
if desc:
|
||||||
doc.append(f'<DD>{desc}')
|
doc.append(f"<DD>{desc}")
|
||||||
|
|
||||||
|
|
||||||
def append_list_end(doc: BookmarkDocument):
|
def append_list_end(doc: BookmarkDocument):
|
||||||
doc.append('</DL><p>')
|
doc.append("</DL><p>")
|
||||||
|
|
|
@ -15,7 +15,7 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# register mime type for .ico files, which is not included in the default
|
# register mime type for .ico files, which is not included in the default
|
||||||
# mimetypes of the Docker image
|
# mimetypes of the Docker image
|
||||||
mimetypes.add_type('image/x-icon', '.ico')
|
mimetypes.add_type("image/x-icon", ".ico")
|
||||||
|
|
||||||
|
|
||||||
def _ensure_favicon_folder():
|
def _ensure_favicon_folder():
|
||||||
|
@ -23,16 +23,16 @@ def _ensure_favicon_folder():
|
||||||
|
|
||||||
|
|
||||||
def _url_to_filename(url: str) -> str:
|
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:
|
def _get_url_parameters(url: str) -> dict:
|
||||||
parsed_uri = urlparse(url)
|
parsed_uri = urlparse(url)
|
||||||
return {
|
return {
|
||||||
# https://example.com/foo?bar -> https://example.com
|
# 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
|
# 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
|
# Create favicon folder if not exists
|
||||||
_ensure_favicon_folder()
|
_ensure_favicon_folder()
|
||||||
# Use scheme+hostname as favicon filename to reuse icon for all pages on the same domain
|
# 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)
|
favicon_file = _check_existing_favicon(favicon_name)
|
||||||
|
|
||||||
if not favicon_file:
|
if not favicon_file:
|
||||||
# Load favicon from provider, save to file
|
# Load favicon from provider, save to file
|
||||||
favicon_url = settings.LD_FAVICON_PROVIDER.format(**url_parameters)
|
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:
|
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)
|
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)
|
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):
|
for chunk in response.iter_content(chunk_size=8192):
|
||||||
file.write(chunk)
|
file.write(chunk)
|
||||||
logger.debug(f'Saved favicon as: {favicon_path}')
|
logger.debug(f"Saved favicon as: {favicon_path}")
|
||||||
|
|
||||||
return favicon_file
|
return favicon_file
|
||||||
|
|
|
@ -55,18 +55,20 @@ class TagCache:
|
||||||
self.cache[tag.name.lower()] = tag
|
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()
|
result = ImportResult()
|
||||||
import_start = timezone.now()
|
import_start = timezone.now()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
netscape_bookmarks = parse(html)
|
netscape_bookmarks = parse(html)
|
||||||
except:
|
except:
|
||||||
logging.exception('Could not read bookmarks file.')
|
logging.exception("Could not read bookmarks file.")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
parse_end = timezone.now()
|
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 and cache all tags beforehand
|
||||||
_create_missing_tags(netscape_bookmarks, user)
|
_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)
|
tasks.schedule_bookmarks_without_favicons(user)
|
||||||
|
|
||||||
end = timezone.now()
|
end = timezone.now()
|
||||||
logger.debug(f'Import duration: {end - import_start}')
|
logger.debug(f"Import duration: {end - import_start}")
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@ -110,7 +112,7 @@ def _get_batches(items: List, batch_size: int):
|
||||||
num_items = len(items)
|
num_items = len(items)
|
||||||
|
|
||||||
while offset < num_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:
|
if len(batch) > 0:
|
||||||
batches.append(batch)
|
batches.append(batch)
|
||||||
offset = offset + batch_size
|
offset = offset + batch_size
|
||||||
|
@ -118,11 +120,13 @@ def _get_batches(items: List, batch_size: int):
|
||||||
return batches
|
return batches
|
||||||
|
|
||||||
|
|
||||||
def _import_batch(netscape_bookmarks: List[NetscapeBookmark],
|
def _import_batch(
|
||||||
|
netscape_bookmarks: List[NetscapeBookmark],
|
||||||
user: User,
|
user: User,
|
||||||
options: ImportOptions,
|
options: ImportOptions,
|
||||||
tag_cache: TagCache,
|
tag_cache: TagCache,
|
||||||
result: ImportResult):
|
result: ImportResult,
|
||||||
|
):
|
||||||
# Query existing bookmarks
|
# Query existing bookmarks
|
||||||
batch_urls = [bookmark.href for bookmark in netscape_bookmarks]
|
batch_urls = [bookmark.href for bookmark in netscape_bookmarks]
|
||||||
existing_bookmarks = Bookmark.objects.filter(owner=user, url__in=batch_urls)
|
existing_bookmarks = Bookmark.objects.filter(owner=user, url__in=batch_urls)
|
||||||
|
@ -136,7 +140,13 @@ def _import_batch(netscape_bookmarks: List[NetscapeBookmark],
|
||||||
try:
|
try:
|
||||||
# Lookup existing bookmark by URL, or create new bookmark if there is no bookmark for that URL yet
|
# Lookup existing bookmark by URL, or create new bookmark if there is no bookmark for that URL yet
|
||||||
bookmark = next(
|
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:
|
if not bookmark:
|
||||||
bookmark = Bookmark(owner=user)
|
bookmark = Bookmark(owner=user)
|
||||||
is_update = False
|
is_update = False
|
||||||
|
@ -146,7 +156,7 @@ def _import_batch(netscape_bookmarks: List[NetscapeBookmark],
|
||||||
_copy_bookmark_data(netscape_bookmark, bookmark, options)
|
_copy_bookmark_data(netscape_bookmark, bookmark, options)
|
||||||
# Validate bookmark fields, exclude owner to prevent n+1 database query,
|
# Validate bookmark fields, exclude owner to prevent n+1 database query,
|
||||||
# also there is no specific validation on owner
|
# also there is no specific validation on owner
|
||||||
bookmark.clean_fields(exclude=['owner'])
|
bookmark.clean_fields(exclude=["owner"])
|
||||||
# Schedule for update or insert
|
# Schedule for update or insert
|
||||||
if is_update:
|
if is_update:
|
||||||
bookmarks_to_update.append(bookmark)
|
bookmarks_to_update.append(bookmark)
|
||||||
|
@ -155,20 +165,25 @@ def _import_batch(netscape_bookmarks: List[NetscapeBookmark],
|
||||||
|
|
||||||
result.success = result.success + 1
|
result.success = result.success + 1
|
||||||
except:
|
except:
|
||||||
shortened_bookmark_tag_str = str(netscape_bookmark)[:100] + '...'
|
shortened_bookmark_tag_str = str(netscape_bookmark)[:100] + "..."
|
||||||
logging.exception('Error importing bookmark: ' + shortened_bookmark_tag_str)
|
logging.exception("Error importing bookmark: " + shortened_bookmark_tag_str)
|
||||||
result.failed = result.failed + 1
|
result.failed = result.failed + 1
|
||||||
|
|
||||||
# Bulk update bookmarks in DB
|
# Bulk update bookmarks in DB
|
||||||
Bookmark.objects.bulk_update(bookmarks_to_update, ['url',
|
Bookmark.objects.bulk_update(
|
||||||
'date_added',
|
bookmarks_to_update,
|
||||||
'date_modified',
|
[
|
||||||
'unread',
|
"url",
|
||||||
'shared',
|
"date_added",
|
||||||
'title',
|
"date_modified",
|
||||||
'description',
|
"unread",
|
||||||
'notes',
|
"shared",
|
||||||
'owner'])
|
"title",
|
||||||
|
"description",
|
||||||
|
"notes",
|
||||||
|
"owner",
|
||||||
|
],
|
||||||
|
)
|
||||||
# Bulk insert new bookmarks into DB
|
# Bulk insert new bookmarks into DB
|
||||||
Bookmark.objects.bulk_create(bookmarks_to_create)
|
Bookmark.objects.bulk_create(bookmarks_to_create)
|
||||||
|
|
||||||
|
@ -183,13 +198,20 @@ def _import_batch(netscape_bookmarks: List[NetscapeBookmark],
|
||||||
for netscape_bookmark in netscape_bookmarks:
|
for netscape_bookmark in netscape_bookmarks:
|
||||||
# Lookup bookmark by URL again
|
# Lookup bookmark by URL again
|
||||||
bookmark = next(
|
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:
|
if not bookmark:
|
||||||
# Something is wrong, we should have just created this 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(
|
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
|
continue
|
||||||
|
|
||||||
# Get tag models by string, schedule inserts for bookmark -> tag associations
|
# 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)
|
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
|
bookmark.url = netscape_bookmark.href
|
||||||
if netscape_bookmark.date_added:
|
if netscape_bookmark.date_added:
|
||||||
bookmark.date_added = parse_timestamp(netscape_bookmark.date_added)
|
bookmark.date_added = parse_timestamp(netscape_bookmark.date_added)
|
||||||
|
|
|
@ -25,29 +25,29 @@ class BookmarkParser(HTMLParser):
|
||||||
|
|
||||||
self.current_tag = None
|
self.current_tag = None
|
||||||
self.bookmark = None
|
self.bookmark = None
|
||||||
self.href = ''
|
self.href = ""
|
||||||
self.add_date = ''
|
self.add_date = ""
|
||||||
self.tags = ''
|
self.tags = ""
|
||||||
self.title = ''
|
self.title = ""
|
||||||
self.description = ''
|
self.description = ""
|
||||||
self.notes = ''
|
self.notes = ""
|
||||||
self.toread = ''
|
self.toread = ""
|
||||||
self.private = ''
|
self.private = ""
|
||||||
|
|
||||||
def handle_starttag(self, tag: str, attrs: list):
|
def handle_starttag(self, tag: str, attrs: list):
|
||||||
name = 'handle_start_' + tag.lower()
|
name = "handle_start_" + tag.lower()
|
||||||
if name in dir(self):
|
if name in dir(self):
|
||||||
getattr(self, name)({k.lower(): v for k, v in attrs})
|
getattr(self, name)({k.lower(): v for k, v in attrs})
|
||||||
self.current_tag = tag
|
self.current_tag = tag
|
||||||
|
|
||||||
def handle_endtag(self, tag: str):
|
def handle_endtag(self, tag: str):
|
||||||
name = 'handle_end_' + tag.lower()
|
name = "handle_end_" + tag.lower()
|
||||||
if name in dir(self):
|
if name in dir(self):
|
||||||
getattr(self, name)()
|
getattr(self, name)()
|
||||||
self.current_tag = None
|
self.current_tag = None
|
||||||
|
|
||||||
def handle_data(self, data):
|
def handle_data(self, data):
|
||||||
name = f'handle_{self.current_tag}_data'
|
name = f"handle_{self.current_tag}_data"
|
||||||
if name in dir(self):
|
if name in dir(self):
|
||||||
getattr(self, name)(data)
|
getattr(self, name)(data)
|
||||||
|
|
||||||
|
@ -60,22 +60,22 @@ class BookmarkParser(HTMLParser):
|
||||||
def handle_start_a(self, attrs: Dict[str, str]):
|
def handle_start_a(self, attrs: Dict[str, str]):
|
||||||
vars(self).update(attrs)
|
vars(self).update(attrs)
|
||||||
tag_names = parse_tag_string(self.tags)
|
tag_names = parse_tag_string(self.tags)
|
||||||
archived = 'linkding:archived' in self.tags
|
archived = "linkding:archived" in self.tags
|
||||||
try:
|
try:
|
||||||
tag_names.remove('linkding:archived')
|
tag_names.remove("linkding:archived")
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
self.bookmark = NetscapeBookmark(
|
self.bookmark = NetscapeBookmark(
|
||||||
href=self.href,
|
href=self.href,
|
||||||
title='',
|
title="",
|
||||||
description='',
|
description="",
|
||||||
notes='',
|
notes="",
|
||||||
date_added=self.add_date,
|
date_added=self.add_date,
|
||||||
tag_names=tag_names,
|
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
|
# Mark as private by default, also when attribute is not specified
|
||||||
private=self.private != '0',
|
private=self.private != "0",
|
||||||
archived=archived,
|
archived=archived,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -84,9 +84,9 @@ class BookmarkParser(HTMLParser):
|
||||||
|
|
||||||
def handle_dd_data(self, data):
|
def handle_dd_data(self, data):
|
||||||
desc = data.strip()
|
desc = data.strip()
|
||||||
if '[linkding-notes]' in desc:
|
if "[linkding-notes]" in desc:
|
||||||
self.notes = desc.split('[linkding-notes]')[1].split('[/linkding-notes]')[0]
|
self.notes = desc.split("[linkding-notes]")[1].split("[/linkding-notes]")[0]
|
||||||
self.description = desc.split('[linkding-notes]')[0]
|
self.description = desc.split("[linkding-notes]")[0]
|
||||||
|
|
||||||
def add_bookmark(self):
|
def add_bookmark(self):
|
||||||
if self.bookmark:
|
if self.bookmark:
|
||||||
|
@ -95,14 +95,14 @@ class BookmarkParser(HTMLParser):
|
||||||
self.bookmark.notes = self.notes
|
self.bookmark.notes = self.notes
|
||||||
self.bookmarks.append(self.bookmark)
|
self.bookmarks.append(self.bookmark)
|
||||||
self.bookmark = None
|
self.bookmark = None
|
||||||
self.href = ''
|
self.href = ""
|
||||||
self.add_date = ''
|
self.add_date = ""
|
||||||
self.tags = ''
|
self.tags = ""
|
||||||
self.title = ''
|
self.title = ""
|
||||||
self.description = ''
|
self.description = ""
|
||||||
self.notes = ''
|
self.notes = ""
|
||||||
self.toread = ''
|
self.toread = ""
|
||||||
self.private = ''
|
self.private = ""
|
||||||
|
|
||||||
|
|
||||||
def parse(html: str) -> List[NetscapeBookmark]:
|
def parse(html: str) -> List[NetscapeBookmark]:
|
||||||
|
|
|
@ -13,7 +13,7 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
def get_or_create_tags(tag_names: List[str], user: User):
|
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]
|
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):
|
def get_or_create_tag(name: str, user: User):
|
||||||
|
|
|
@ -18,8 +18,10 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
def is_web_archive_integration_active(user: User) -> bool:
|
def is_web_archive_integration_active(user: User) -> bool:
|
||||||
background_tasks_enabled = not settings.LD_DISABLE_BACKGROUND_TASKS
|
background_tasks_enabled = not settings.LD_DISABLE_BACKGROUND_TASKS
|
||||||
web_archive_integration_enabled = \
|
web_archive_integration_enabled = (
|
||||||
user.profile.web_archive_integration == UserProfile.WEB_ARCHIVE_INTEGRATION_ENABLED
|
user.profile.web_archive_integration
|
||||||
|
== UserProfile.WEB_ARCHIVE_INTEGRATION_ENABLED
|
||||||
|
)
|
||||||
|
|
||||||
return background_tasks_enabled and 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):
|
def _load_newest_snapshot(bookmark: Bookmark):
|
||||||
try:
|
try:
|
||||||
logger.info(f'Load existing snapshot for bookmark. url={bookmark.url}')
|
logger.info(f"Load existing snapshot for bookmark. url={bookmark.url}")
|
||||||
cdx_api = bookmarks.services.wayback.CustomWaybackMachineCDXServerAPI(bookmark.url)
|
cdx_api = bookmarks.services.wayback.CustomWaybackMachineCDXServerAPI(
|
||||||
|
bookmark.url
|
||||||
|
)
|
||||||
existing_snapshot = cdx_api.newest()
|
existing_snapshot = cdx_api.newest()
|
||||||
|
|
||||||
if existing_snapshot:
|
if existing_snapshot:
|
||||||
bookmark.web_archive_snapshot_url = existing_snapshot.archive_url
|
bookmark.web_archive_snapshot_url = existing_snapshot.archive_url
|
||||||
bookmark.save(update_fields=['web_archive_snapshot_url'])
|
bookmark.save(update_fields=["web_archive_snapshot_url"])
|
||||||
logger.info(f'Using newest snapshot. url={bookmark.url} from={existing_snapshot.datetime_timestamp}')
|
logger.info(
|
||||||
|
f"Using newest snapshot. url={bookmark.url} from={existing_snapshot.datetime_timestamp}"
|
||||||
|
)
|
||||||
|
|
||||||
except NoCDXRecordFound:
|
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:
|
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):
|
def _create_snapshot(bookmark: Bookmark):
|
||||||
logger.info(f'Create new snapshot for bookmark. url={bookmark.url}...')
|
logger.info(f"Create new snapshot for bookmark. url={bookmark.url}...")
|
||||||
archive = waybackpy.WaybackMachineSaveAPI(bookmark.url, DEFAULT_USER_AGENT, max_tries=1)
|
archive = waybackpy.WaybackMachineSaveAPI(
|
||||||
|
bookmark.url, DEFAULT_USER_AGENT, max_tries=1
|
||||||
|
)
|
||||||
archive.save()
|
archive.save()
|
||||||
bookmark.web_archive_snapshot_url = archive.archive_url
|
bookmark.web_archive_snapshot_url = archive.archive_url
|
||||||
bookmark.save(update_fields=['web_archive_snapshot_url'])
|
bookmark.save(update_fields=["web_archive_snapshot_url"])
|
||||||
logger.info(f'Successfully created new snapshot for bookmark:. url={bookmark.url}')
|
logger.info(f"Successfully created new snapshot for bookmark:. url={bookmark.url}")
|
||||||
|
|
||||||
|
|
||||||
@background()
|
@background()
|
||||||
|
@ -72,10 +82,13 @@ def _create_web_archive_snapshot_task(bookmark_id: int, force_update: bool):
|
||||||
return
|
return
|
||||||
except TooManyRequestsError:
|
except TooManyRequestsError:
|
||||||
logger.error(
|
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:
|
except WaybackError as error:
|
||||||
logger.error(f'Failed to create snapshot, trying to load newest snapshot as fallback. url={bookmark.url}',
|
logger.error(
|
||||||
exc_info=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 the newest snapshot as fallback
|
||||||
_load_newest_snapshot(bookmark)
|
_load_newest_snapshot(bookmark)
|
||||||
|
@ -102,7 +115,9 @@ def schedule_bookmarks_without_snapshots(user: User):
|
||||||
@background()
|
@background()
|
||||||
def _schedule_bookmarks_without_snapshots_task(user_id: int):
|
def _schedule_bookmarks_without_snapshots_task(user_id: int):
|
||||||
user = get_user_model().objects.get(id=user_id)
|
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:
|
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
|
# 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:
|
except Bookmark.DoesNotExist:
|
||||||
return
|
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)
|
new_favicon_file = favicon_loader.load_favicon(bookmark.url)
|
||||||
|
|
||||||
if new_favicon_file != bookmark.favicon_file:
|
if new_favicon_file != bookmark.favicon_file:
|
||||||
bookmark.favicon_file = new_favicon_file
|
bookmark.favicon_file = new_favicon_file
|
||||||
bookmark.save(update_fields=['favicon_file'])
|
bookmark.save(update_fields=["favicon_file"])
|
||||||
logger.info(f'Successfully updated favicon for bookmark. url={bookmark.url} icon={new_favicon_file}')
|
logger.info(
|
||||||
|
f"Successfully updated favicon for bookmark. url={bookmark.url} icon={new_favicon_file}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def schedule_bookmarks_without_favicons(user: User):
|
def schedule_bookmarks_without_favicons(user: User):
|
||||||
|
@ -146,11 +163,13 @@ def schedule_bookmarks_without_favicons(user: User):
|
||||||
@background()
|
@background()
|
||||||
def _schedule_bookmarks_without_favicons_task(user_id: int):
|
def _schedule_bookmarks_without_favicons_task(user_id: int):
|
||||||
user = get_user_model().objects.get(id=user_id)
|
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 = []
|
tasks = []
|
||||||
|
|
||||||
for bookmark in bookmarks:
|
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)
|
tasks.append(task)
|
||||||
|
|
||||||
Task.objects.bulk_create(tasks)
|
Task.objects.bulk_create(tasks)
|
||||||
|
@ -168,7 +187,9 @@ def _schedule_refresh_favicons_task(user_id: int):
|
||||||
tasks = []
|
tasks = []
|
||||||
|
|
||||||
for bookmark in bookmarks:
|
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)
|
tasks.append(task)
|
||||||
|
|
||||||
Task.objects.bulk_create(tasks)
|
Task.objects.bulk_create(tasks)
|
||||||
|
|
|
@ -14,8 +14,10 @@ class CustomWaybackMachineCDXServerAPI(waybackpy.WaybackMachineCDXServerAPI):
|
||||||
|
|
||||||
def newest(self):
|
def newest(self):
|
||||||
unix_timestamp = int(time.time())
|
unix_timestamp = int(time.time())
|
||||||
self.closest = waybackpy.utils.unix_timestamp_to_wayback_timestamp(unix_timestamp)
|
self.closest = waybackpy.utils.unix_timestamp_to_wayback_timestamp(
|
||||||
self.sort = 'closest'
|
unix_timestamp
|
||||||
|
)
|
||||||
|
self.sort = "closest"
|
||||||
self.limit = -5
|
self.limit = -5
|
||||||
|
|
||||||
newest_snapshot = None
|
newest_snapshot = None
|
||||||
|
@ -37,4 +39,4 @@ class CustomWaybackMachineCDXServerAPI(waybackpy.WaybackMachineCDXServerAPI):
|
||||||
super().add_payload(payload)
|
super().add_payload(payload)
|
||||||
# Set fastLatest query param, as we are only using this API to get the latest snapshot and using fastLatest
|
# 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
|
# makes searching for latest snapshots faster
|
||||||
payload['fastLatest'] = 'true'
|
payload["fastLatest"] = "true"
|
||||||
|
|
|
@ -18,9 +18,9 @@ class WebsiteMetadata:
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
return {
|
return {
|
||||||
'url': self.url,
|
"url": self.url,
|
||||||
'title': self.title,
|
"title": self.title,
|
||||||
'description': self.description,
|
"description": self.description,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -34,22 +34,29 @@ def load_website_metadata(url: str):
|
||||||
start = timezone.now()
|
start = timezone.now()
|
||||||
page_text = load_page(url)
|
page_text = load_page(url)
|
||||||
end = timezone.now()
|
end = timezone.now()
|
||||||
logger.debug(f'Load duration: {end - start}')
|
logger.debug(f"Load duration: {end - start}")
|
||||||
|
|
||||||
start = timezone.now()
|
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
|
title = soup.title.string.strip() if soup.title is not None else None
|
||||||
description_tag = soup.find('meta', attrs={'name': 'description'})
|
description_tag = soup.find("meta", attrs={"name": "description"})
|
||||||
description = description_tag['content'].strip() if description_tag and description_tag[
|
description = (
|
||||||
'content'] else None
|
description_tag["content"].strip()
|
||||||
|
if description_tag and description_tag["content"]
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
|
||||||
if not description:
|
if not description:
|
||||||
description_tag = soup.find('meta', attrs={'property': 'og: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 = (
|
||||||
|
description_tag["content"].strip()
|
||||||
|
if description_tag and description_tag["content"]
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
|
||||||
end = timezone.now()
|
end = timezone.now()
|
||||||
logger.debug(f'Parsing duration: {end - start}')
|
logger.debug(f"Parsing duration: {end - start}")
|
||||||
finally:
|
finally:
|
||||||
return WebsiteMetadata(url=url, title=title, description=description)
|
return WebsiteMetadata(url=url, title=title, description=description)
|
||||||
|
|
||||||
|
@ -73,30 +80,30 @@ def load_page(url: str):
|
||||||
else:
|
else:
|
||||||
content = content + chunk
|
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
|
# Stop reading if we have parsed end of head tag
|
||||||
end_of_head = '</head>'.encode('utf-8')
|
end_of_head = "</head>".encode("utf-8")
|
||||||
if end_of_head in content:
|
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
|
content = content.split(end_of_head)[0] + end_of_head
|
||||||
break
|
break
|
||||||
# Stop reading if we exceed limit
|
# Stop reading if we exceed limit
|
||||||
if size > MAX_CONTENT_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
|
break
|
||||||
if hasattr(r, '_content_consumed'):
|
if hasattr(r, "_content_consumed"):
|
||||||
logger.debug(f'Request consumed: {r._content_consumed}')
|
logger.debug(f"Request consumed: {r._content_consumed}")
|
||||||
|
|
||||||
# Use charset_normalizer to determine encoding that best matches the response content
|
# 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
|
# 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,
|
# This is different from Response.text which does respect the encoding specified in the response first,
|
||||||
# before trying to determine one
|
# before trying to determine one
|
||||||
results = from_bytes(content or '')
|
results = from_bytes(content or "")
|
||||||
return str(results.best())
|
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():
|
def fake_request_headers():
|
||||||
|
|
|
@ -15,9 +15,11 @@ def user_logged_in(sender, request, user, **kwargs):
|
||||||
def extend_sqlite(connection=None, **kwargs):
|
def extend_sqlite(connection=None, **kwargs):
|
||||||
# Load ICU extension into Sqlite connection to support case-insensitive
|
# Load ICU extension into Sqlite connection to support case-insensitive
|
||||||
# comparisons with unicode characters
|
# 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.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:
|
with connection.cursor() as cursor:
|
||||||
# Load an ICU collation for case-insensitive ordering.
|
# Load an ICU collation for case-insensitive ordering.
|
||||||
|
|
|
@ -2,48 +2,67 @@ from typing import List
|
||||||
|
|
||||||
from django import template
|
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 = template.Library()
|
||||||
|
|
||||||
|
|
||||||
@register.inclusion_tag('bookmarks/form.html', name='bookmark_form', takes_context=True)
|
@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):
|
def bookmark_form(
|
||||||
|
context,
|
||||||
|
form: BookmarkForm,
|
||||||
|
cancel_url: str,
|
||||||
|
bookmark_id: int = 0,
|
||||||
|
auto_close: bool = False,
|
||||||
|
):
|
||||||
return {
|
return {
|
||||||
'request': context['request'],
|
"request": context["request"],
|
||||||
'form': form,
|
"form": form,
|
||||||
'auto_close': auto_close,
|
"auto_close": auto_close,
|
||||||
'bookmark_id': bookmark_id,
|
"bookmark_id": bookmark_id,
|
||||||
'cancel_url': cancel_url
|
"cancel_url": cancel_url,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@register.inclusion_tag('bookmarks/search.html', name='bookmark_search', takes_context=True)
|
@register.inclusion_tag(
|
||||||
def bookmark_search(context, search: BookmarkSearch, tags: [Tag], mode: str = ''):
|
"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]
|
tag_names = [tag.name for tag in tags]
|
||||||
tags_string = build_tag_string(tag_names, ' ')
|
tags_string = build_tag_string(tag_names, " ")
|
||||||
search_form = BookmarkSearchForm(search, editable_fields=['q'])
|
search_form = BookmarkSearchForm(search, editable_fields=["q"])
|
||||||
|
|
||||||
if mode == 'shared':
|
if mode == "shared":
|
||||||
preferences_form = BookmarkSearchForm(search, editable_fields=['sort'])
|
preferences_form = BookmarkSearchForm(search, editable_fields=["sort"])
|
||||||
else:
|
else:
|
||||||
preferences_form = BookmarkSearchForm(search, editable_fields=['sort', 'shared', 'unread'])
|
preferences_form = BookmarkSearchForm(
|
||||||
|
search, editable_fields=["sort", "shared", "unread"]
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
'request': context['request'],
|
"request": context["request"],
|
||||||
'search': search,
|
"search": search,
|
||||||
'search_form': search_form,
|
"search_form": search_form,
|
||||||
'preferences_form': preferences_form,
|
"preferences_form": preferences_form,
|
||||||
'tags_string': tags_string,
|
"tags_string": tags_string,
|
||||||
'mode': mode,
|
"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]):
|
def user_select(context, search: BookmarkSearch, users: List[User]):
|
||||||
sorted_users = sorted(users, key=lambda x: str.lower(x.username))
|
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 {
|
return {
|
||||||
'search': search,
|
"search": search,
|
||||||
'users': sorted_users,
|
"users": sorted_users,
|
||||||
'form': form,
|
"form": form,
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,14 +8,15 @@ NUM_ADJACENT_PAGES = 2
|
||||||
register = template.Library()
|
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):
|
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 {
|
return {"page": page, "visible_page_numbers": visible_page_numbers}
|
||||||
'page': page,
|
|
||||||
'visible_page_numbers': visible_page_numbers
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def get_visible_page_numbers(current_page_number: int, num_pages: int) -> [int]:
|
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()
|
visible_pages = set()
|
||||||
|
|
||||||
# Add adjacent pages around current page
|
# Add adjacent pages around current page
|
||||||
visible_pages |= set(range(
|
visible_pages |= set(
|
||||||
|
range(
|
||||||
max(1, current_page_number - NUM_ADJACENT_PAGES),
|
max(1, current_page_number - NUM_ADJACENT_PAGES),
|
||||||
min(num_pages, current_page_number + NUM_ADJACENT_PAGES) + 1
|
min(num_pages, current_page_number + NUM_ADJACENT_PAGES) + 1,
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# Add first page
|
# Add first page
|
||||||
visible_pages.add(1)
|
visible_pages.add(1)
|
||||||
|
|
|
@ -28,12 +28,12 @@ def add_tag_to_query(context, tag_name: str):
|
||||||
params = context.request.GET.copy()
|
params = context.request.GET.copy()
|
||||||
|
|
||||||
# Append to or create query string
|
# Append to or create query string
|
||||||
if params.__contains__('q'):
|
if params.__contains__("q"):
|
||||||
query_string = params.__getitem__('q') + ' '
|
query_string = params.__getitem__("q") + " "
|
||||||
else:
|
else:
|
||||||
query_string = ''
|
query_string = ""
|
||||||
query_string = query_string + '#' + tag_name
|
query_string = query_string + "#" + tag_name
|
||||||
params.__setitem__('q', query_string)
|
params.__setitem__("q", query_string)
|
||||||
|
|
||||||
return params.urlencode()
|
return params.urlencode()
|
||||||
|
|
||||||
|
@ -41,20 +41,26 @@ def add_tag_to_query(context, tag_name: str):
|
||||||
@register.simple_tag(takes_context=True)
|
@register.simple_tag(takes_context=True)
|
||||||
def remove_tag_from_query(context, tag_name: str):
|
def remove_tag_from_query(context, tag_name: str):
|
||||||
params = context.request.GET.copy()
|
params = context.request.GET.copy()
|
||||||
if params.__contains__('q'):
|
if params.__contains__("q"):
|
||||||
# Split query string into parts
|
# Split query string into parts
|
||||||
query_string = params.__getitem__('q')
|
query_string = params.__getitem__("q")
|
||||||
query_parts = query_string.split()
|
query_parts = query_string.split()
|
||||||
# Remove tag with hash
|
# Remove tag with hash
|
||||||
tag_name_with_hash = '#' + tag_name
|
tag_name_with_hash = "#" + tag_name
|
||||||
query_parts = [part for part in query_parts if str.lower(part) != str.lower(tag_name_with_hash)]
|
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
|
# When using lax tag search, also remove tag without hash
|
||||||
profile = context.request.user_profile
|
profile = context.request.user_profile
|
||||||
if profile.tag_search == UserProfile.TAG_SEARCH_LAX:
|
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
|
# Rebuild query string
|
||||||
query_string = ' '.join(query_parts)
|
query_string = " ".join(query_parts)
|
||||||
params.__setitem__('q', query_string)
|
params.__setitem__("q", query_string)
|
||||||
|
|
||||||
return params.urlencode()
|
return params.urlencode()
|
||||||
|
|
||||||
|
@ -71,38 +77,38 @@ def replace_query_param(context, **kwargs):
|
||||||
return query.urlencode()
|
return query.urlencode()
|
||||||
|
|
||||||
|
|
||||||
@register.filter(name='hash_tag')
|
@register.filter(name="hash_tag")
|
||||||
def hash_tag(tag_name):
|
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):
|
def first_char(text):
|
||||||
return text[0]
|
return text[0]
|
||||||
|
|
||||||
|
|
||||||
@register.filter(name='remaining_chars')
|
@register.filter(name="remaining_chars")
|
||||||
def remaining_chars(text, index):
|
def remaining_chars(text, index):
|
||||||
return text[index:]
|
return text[index:]
|
||||||
|
|
||||||
|
|
||||||
@register.filter(name='humanize_absolute_date')
|
@register.filter(name="humanize_absolute_date")
|
||||||
def humanize_absolute_date(value):
|
def humanize_absolute_date(value):
|
||||||
if value in (None, ''):
|
if value in (None, ""):
|
||||||
return ''
|
return ""
|
||||||
return utils.humanize_absolute_date(value)
|
return utils.humanize_absolute_date(value)
|
||||||
|
|
||||||
|
|
||||||
@register.filter(name='humanize_relative_date')
|
@register.filter(name="humanize_relative_date")
|
||||||
def humanize_relative_date(value):
|
def humanize_relative_date(value):
|
||||||
if value in (None, ''):
|
if value in (None, ""):
|
||||||
return ''
|
return ""
|
||||||
return utils.humanize_relative_date(value)
|
return utils.humanize_relative_date(value)
|
||||||
|
|
||||||
|
|
||||||
@register.tag
|
@register.tag
|
||||||
def htmlmin(parser, token):
|
def htmlmin(parser, token):
|
||||||
nodelist = parser.parse(('endhtmlmin',))
|
nodelist = parser.parse(("endhtmlmin",))
|
||||||
parser.delete_first_token()
|
parser.delete_first_token()
|
||||||
return HtmlMinNode(nodelist)
|
return HtmlMinNode(nodelist)
|
||||||
|
|
||||||
|
@ -114,7 +120,7 @@ class HtmlMinNode(template.Node):
|
||||||
def render(self, context):
|
def render(self, context):
|
||||||
output = self.nodelist.render(context)
|
output = self.nodelist.render(context)
|
||||||
|
|
||||||
output = re.sub(r'\s+', ' ', output)
|
output = re.sub(r"\s+", " ", output)
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
@ -123,11 +129,11 @@ class HtmlMinNode(template.Node):
|
||||||
def render_markdown(context, markdown_text):
|
def render_markdown(context, markdown_text):
|
||||||
# naive approach to reusing the renderer for a single request
|
# naive approach to reusing the renderer for a single request
|
||||||
# works for bookmark list for now
|
# works for bookmark list for now
|
||||||
if not ('markdown_renderer' in context):
|
if not ("markdown_renderer" in context):
|
||||||
renderer = markdown.Markdown(extensions=['fenced_code', 'nl2br'])
|
renderer = markdown.Markdown(extensions=["fenced_code", "nl2br"])
|
||||||
context['markdown_renderer'] = renderer
|
context["markdown_renderer"] = renderer
|
||||||
else:
|
else:
|
||||||
renderer = context['markdown_renderer']
|
renderer = context["markdown_renderer"]
|
||||||
|
|
||||||
as_html = renderer.convert(markdown_text)
|
as_html = renderer.convert(markdown_text)
|
||||||
sanitized_html = bleach.clean(as_html, markdown_tags, markdown_attrs)
|
sanitized_html = bleach.clean(as_html, markdown_tags, markdown_attrs)
|
||||||
|
|
|
@ -18,24 +18,27 @@ class BookmarkFactoryMixin:
|
||||||
|
|
||||||
def get_or_create_test_user(self):
|
def get_or_create_test_user(self):
|
||||||
if self.user is None:
|
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
|
return self.user
|
||||||
|
|
||||||
def setup_bookmark(self,
|
def setup_bookmark(
|
||||||
|
self,
|
||||||
is_archived: bool = False,
|
is_archived: bool = False,
|
||||||
unread: bool = False,
|
unread: bool = False,
|
||||||
shared: bool = False,
|
shared: bool = False,
|
||||||
tags=None,
|
tags=None,
|
||||||
user: User = None,
|
user: User = None,
|
||||||
url: str = '',
|
url: str = "",
|
||||||
title: str = None,
|
title: str = None,
|
||||||
description: str = '',
|
description: str = "",
|
||||||
notes: str = '',
|
notes: str = "",
|
||||||
website_title: str = '',
|
website_title: str = "",
|
||||||
website_description: str = '',
|
website_description: str = "",
|
||||||
web_archive_snapshot_url: str = '',
|
web_archive_snapshot_url: str = "",
|
||||||
favicon_file: str = '',
|
favicon_file: str = "",
|
||||||
added: datetime = None,
|
added: datetime = None,
|
||||||
):
|
):
|
||||||
if title is None:
|
if title is None:
|
||||||
|
@ -46,7 +49,7 @@ class BookmarkFactoryMixin:
|
||||||
user = self.get_or_create_test_user()
|
user = self.get_or_create_test_user()
|
||||||
if not url:
|
if not url:
|
||||||
unique_id = get_random_string(length=32)
|
unique_id = get_random_string(length=32)
|
||||||
url = 'https://example.com/' + unique_id
|
url = "https://example.com/" + unique_id
|
||||||
if added is None:
|
if added is None:
|
||||||
added = timezone.now()
|
added = timezone.now()
|
||||||
bookmark = Bookmark(
|
bookmark = Bookmark(
|
||||||
|
@ -71,49 +74,53 @@ class BookmarkFactoryMixin:
|
||||||
bookmark.save()
|
bookmark.save()
|
||||||
return bookmark
|
return bookmark
|
||||||
|
|
||||||
def setup_numbered_bookmarks(self,
|
def setup_numbered_bookmarks(
|
||||||
|
self,
|
||||||
count: int,
|
count: int,
|
||||||
prefix: str = '',
|
prefix: str = "",
|
||||||
suffix: str = '',
|
suffix: str = "",
|
||||||
tag_prefix: str = '',
|
tag_prefix: str = "",
|
||||||
archived: bool = False,
|
archived: bool = False,
|
||||||
unread: bool = False,
|
unread: bool = False,
|
||||||
shared: bool = False,
|
shared: bool = False,
|
||||||
with_tags: bool = False,
|
with_tags: bool = False,
|
||||||
user: User = None):
|
user: User = None,
|
||||||
|
):
|
||||||
user = user or self.get_or_create_test_user()
|
user = user or self.get_or_create_test_user()
|
||||||
bookmarks = []
|
bookmarks = []
|
||||||
|
|
||||||
if not prefix:
|
if not prefix:
|
||||||
if archived:
|
if archived:
|
||||||
prefix = 'Archived Bookmark'
|
prefix = "Archived Bookmark"
|
||||||
elif shared:
|
elif shared:
|
||||||
prefix = 'Shared Bookmark'
|
prefix = "Shared Bookmark"
|
||||||
else:
|
else:
|
||||||
prefix = 'Bookmark'
|
prefix = "Bookmark"
|
||||||
|
|
||||||
if not tag_prefix:
|
if not tag_prefix:
|
||||||
if archived:
|
if archived:
|
||||||
tag_prefix = 'Archived Tag'
|
tag_prefix = "Archived Tag"
|
||||||
elif shared:
|
elif shared:
|
||||||
tag_prefix = 'Shared Tag'
|
tag_prefix = "Shared Tag"
|
||||||
else:
|
else:
|
||||||
tag_prefix = 'Tag'
|
tag_prefix = "Tag"
|
||||||
|
|
||||||
for i in range(1, count + 1):
|
for i in range(1, count + 1):
|
||||||
title = f'{prefix} {i}{suffix}'
|
title = f"{prefix} {i}{suffix}"
|
||||||
url = f'https://example.com/{prefix}/{i}'
|
url = f"https://example.com/{prefix}/{i}"
|
||||||
tags = []
|
tags = []
|
||||||
if with_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)]
|
tags = [self.setup_tag(name=tag_name, user=user)]
|
||||||
bookmark = self.setup_bookmark(url=url,
|
bookmark = self.setup_bookmark(
|
||||||
|
url=url,
|
||||||
title=title,
|
title=title,
|
||||||
is_archived=archived,
|
is_archived=archived,
|
||||||
unread=unread,
|
unread=unread,
|
||||||
shared=shared,
|
shared=shared,
|
||||||
tags=tags,
|
tags=tags,
|
||||||
user=user)
|
user=user,
|
||||||
|
)
|
||||||
bookmarks.append(bookmark)
|
bookmarks.append(bookmark)
|
||||||
|
|
||||||
return bookmarks
|
return bookmarks
|
||||||
|
@ -121,7 +128,7 @@ class BookmarkFactoryMixin:
|
||||||
def get_numbered_bookmark(self, title: str):
|
def get_numbered_bookmark(self, title: str):
|
||||||
return Bookmark.objects.get(title=title)
|
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:
|
if user is None:
|
||||||
user = self.get_or_create_test_user()
|
user = self.get_or_create_test_user()
|
||||||
if not name:
|
if not name:
|
||||||
|
@ -130,10 +137,15 @@ class BookmarkFactoryMixin:
|
||||||
tag.save()
|
tag.save()
|
||||||
return tag
|
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:
|
if not name:
|
||||||
name = get_random_string(length=32)
|
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_sharing = enable_sharing
|
||||||
user.profile.enable_public_sharing = enable_public_sharing
|
user.profile.enable_public_sharing = enable_public_sharing
|
||||||
user.profile.save()
|
user.profile.save()
|
||||||
|
@ -161,17 +173,17 @@ class LinkdingApiTestCase(APITestCase):
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def post(self, url, data=None, expected_status_code=status.HTTP_200_OK):
|
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)
|
self.assertEqual(response.status_code, expected_status_code)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def put(self, url, data=None, expected_status_code=status.HTTP_200_OK):
|
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)
|
self.assertEqual(response.status_code, expected_status_code)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def patch(self, url, data=None, expected_status_code=status.HTTP_200_OK):
|
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)
|
self.assertEqual(response.status_code, expected_status_code)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
@ -182,14 +194,16 @@ class LinkdingApiTestCase(APITestCase):
|
||||||
|
|
||||||
|
|
||||||
class BookmarkHtmlTag:
|
class BookmarkHtmlTag:
|
||||||
def __init__(self,
|
def __init__(
|
||||||
href: str = '',
|
self,
|
||||||
title: str = '',
|
href: str = "",
|
||||||
description: str = '',
|
title: str = "",
|
||||||
add_date: str = '',
|
description: str = "",
|
||||||
tags: str = '',
|
add_date: str = "",
|
||||||
|
tags: str = "",
|
||||||
to_read: bool = False,
|
to_read: bool = False,
|
||||||
private: bool = True):
|
private: bool = True,
|
||||||
|
):
|
||||||
self.href = href
|
self.href = href
|
||||||
self.title = title
|
self.title = title
|
||||||
self.description = description
|
self.description = description
|
||||||
|
@ -201,7 +215,7 @@ class BookmarkHtmlTag:
|
||||||
|
|
||||||
class ImportTestMixin:
|
class ImportTestMixin:
|
||||||
def render_tag(self, tag: BookmarkHtmlTag):
|
def render_tag(self, tag: BookmarkHtmlTag):
|
||||||
return f'''
|
return f"""
|
||||||
<DT>
|
<DT>
|
||||||
<A {f'HREF="{tag.href}"' if tag.href else ''}
|
<A {f'HREF="{tag.href}"' if tag.href else ''}
|
||||||
{f'ADD_DATE="{tag.add_date}"' if tag.add_date else ''}
|
{f'ADD_DATE="{tag.add_date}"' if tag.add_date else ''}
|
||||||
|
@ -211,13 +225,13 @@ class ImportTestMixin:
|
||||||
{tag.title if tag.title else ''}
|
{tag.title if tag.title else ''}
|
||||||
</A>
|
</A>
|
||||||
{f'<DD>{tag.description}' if tag.description else ''}
|
{f'<DD>{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:
|
if tags:
|
||||||
rendered_tags = [self.render_tag(tag) for tag in tags]
|
rendered_tags = [self.render_tag(tag) for tag in tags]
|
||||||
tags_html = '\n'.join(rendered_tags)
|
tags_html = "\n".join(rendered_tags)
|
||||||
return f'''
|
return f"""
|
||||||
<!DOCTYPE NETSCAPE-Bookmark-file-1>
|
<!DOCTYPE NETSCAPE-Bookmark-file-1>
|
||||||
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">
|
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">
|
||||||
<TITLE>Bookmarks</TITLE>
|
<TITLE>Bookmarks</TITLE>
|
||||||
|
@ -225,34 +239,34 @@ class ImportTestMixin:
|
||||||
<DL><p>
|
<DL><p>
|
||||||
{tags_html}
|
{tags_html}
|
||||||
</DL><p>
|
</DL><p>
|
||||||
'''
|
"""
|
||||||
|
|
||||||
|
|
||||||
_words = [
|
_words = [
|
||||||
'quasi',
|
"quasi",
|
||||||
'consequatur',
|
"consequatur",
|
||||||
'necessitatibus',
|
"necessitatibus",
|
||||||
'debitis',
|
"debitis",
|
||||||
'quod',
|
"quod",
|
||||||
'vero',
|
"vero",
|
||||||
'qui',
|
"qui",
|
||||||
'commodi',
|
"commodi",
|
||||||
'quod',
|
"quod",
|
||||||
'odio',
|
"odio",
|
||||||
'aliquam',
|
"aliquam",
|
||||||
'veniam',
|
"veniam",
|
||||||
'architecto',
|
"architecto",
|
||||||
'consequatur',
|
"consequatur",
|
||||||
'autem',
|
"autem",
|
||||||
'qui',
|
"qui",
|
||||||
'iste',
|
"iste",
|
||||||
'asperiores',
|
"asperiores",
|
||||||
'soluta',
|
"soluta",
|
||||||
'et',
|
"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:
|
if num_words is None:
|
||||||
num_words = random.randint(5, 10)
|
num_words = random.randint(5, 10)
|
||||||
selected_words = random.choices(_words, k=num_words)
|
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)
|
selected_words.append(including_word)
|
||||||
random.shuffle(selected_words)
|
random.shuffle(selected_words)
|
||||||
|
|
||||||
return ' '.join(selected_words)
|
return " ".join(selected_words)
|
||||||
|
|
||||||
|
|
||||||
def disable_logging(f):
|
def disable_logging(f):
|
||||||
|
@ -275,5 +289,5 @@ def disable_logging(f):
|
||||||
|
|
||||||
|
|
||||||
def collapse_whitespace(text: str):
|
def collapse_whitespace(text: str):
|
||||||
text = text.replace('\n', '').replace('\r', '')
|
text = text.replace("\n", "").replace("\r", "")
|
||||||
return ' '.join(text.split())
|
return " ".join(text.split())
|
||||||
|
|
|
@ -6,21 +6,24 @@ from bookmarks.tests.helpers import BookmarkFactoryMixin
|
||||||
|
|
||||||
class AnonymousViewTestCase(TestCase, BookmarkFactoryMixin):
|
class AnonymousViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
def assertSharedBookmarksLinkCount(self, response, count):
|
def assertSharedBookmarksLinkCount(self, response, count):
|
||||||
url = reverse('bookmarks:shared')
|
url = reverse("bookmarks:shared")
|
||||||
self.assertContains(response, f'<a href="{url}" class="btn btn-link">Shared bookmarks</a>',
|
self.assertContains(
|
||||||
count=count)
|
response,
|
||||||
|
f'<a href="{url}" class="btn btn-link">Shared bookmarks</a>',
|
||||||
|
count=count,
|
||||||
|
)
|
||||||
|
|
||||||
def test_publicly_shared_bookmarks_link(self):
|
def test_publicly_shared_bookmarks_link(self):
|
||||||
# should not render link if no public shares exist
|
# should not render link if no public shares exist
|
||||||
user = self.setup_user(enable_sharing=True)
|
user = self.setup_user(enable_sharing=True)
|
||||||
self.setup_bookmark(user=user, shared=True)
|
self.setup_bookmark(user=user, shared=True)
|
||||||
|
|
||||||
response = self.client.get(reverse('login'))
|
response = self.client.get(reverse("login"))
|
||||||
self.assertSharedBookmarksLinkCount(response, 0)
|
self.assertSharedBookmarksLinkCount(response, 0)
|
||||||
|
|
||||||
# should render link if public shares exist
|
# should render link if public shares exist
|
||||||
user.profile.enable_public_sharing = True
|
user.profile.enable_public_sharing = True
|
||||||
user.profile.save()
|
user.profile.save()
|
||||||
|
|
||||||
response = self.client.get(reverse('login'))
|
response = self.client.get(reverse("login"))
|
||||||
self.assertSharedBookmarksLinkCount(response, 1)
|
self.assertSharedBookmarksLinkCount(response, 1)
|
||||||
|
|
|
@ -7,23 +7,35 @@ from django.test import TestCase
|
||||||
|
|
||||||
class AppOptionsTestCase(TestCase):
|
class AppOptionsTestCase(TestCase):
|
||||||
def setUp(self) -> None:
|
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):
|
def test_empty_csrf_trusted_origins(self):
|
||||||
module = importlib.reload(self.settings_module)
|
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):
|
def test_single_csrf_trusted_origin(self):
|
||||||
module = importlib.reload(self.settings_module)
|
module = importlib.reload(self.settings_module)
|
||||||
|
|
||||||
self.assertTrue(hasattr(module, 'CSRF_TRUSTED_ORIGINS'))
|
self.assertTrue(hasattr(module, "CSRF_TRUSTED_ORIGINS"))
|
||||||
self.assertCountEqual(module.CSRF_TRUSTED_ORIGINS, ['https://linkding.example.com'])
|
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):
|
def test_multiple_csrf_trusted_origin(self):
|
||||||
module = importlib.reload(self.settings_module)
|
module = importlib.reload(self.settings_module)
|
||||||
|
|
||||||
self.assertTrue(hasattr(module, 'CSRF_TRUSTED_ORIGINS'))
|
self.assertTrue(hasattr(module, "CSRF_TRUSTED_ORIGINS"))
|
||||||
self.assertCountEqual(module.CSRF_TRUSTED_ORIGINS, ['https://linkding.example.com', 'http://linkding.example.com'])
|
self.assertCountEqual(
|
||||||
|
module.CSRF_TRUSTED_ORIGINS,
|
||||||
|
["https://linkding.example.com", "http://linkding.example.com"],
|
||||||
|
)
|
||||||
|
|
|
@ -10,37 +10,49 @@ class AuthProxySupportTest(TestCase):
|
||||||
# Reproducing configuration from the settings logic here
|
# Reproducing configuration from the settings logic here
|
||||||
# ideally this test would just override the respective options
|
# ideally this test would just override the respective options
|
||||||
@modify_settings(
|
@modify_settings(
|
||||||
MIDDLEWARE={'append': 'bookmarks.middlewares.CustomRemoteUserMiddleware'},
|
MIDDLEWARE={"append": "bookmarks.middlewares.CustomRemoteUserMiddleware"},
|
||||||
AUTHENTICATION_BACKENDS={'prepend': 'django.contrib.auth.backends.RemoteUserBackend'}
|
AUTHENTICATION_BACKENDS={
|
||||||
|
"prepend": "django.contrib.auth.backends.RemoteUserBackend"
|
||||||
|
},
|
||||||
)
|
)
|
||||||
def test_auth_proxy_authentication(self):
|
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}
|
headers = {"REMOTE_USER": user.username}
|
||||||
response = self.client.get(reverse('bookmarks:index'), **headers)
|
response = self.client.get(reverse("bookmarks:index"), **headers)
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
# Reproducing configuration from the settings logic here
|
# Reproducing configuration from the settings logic here
|
||||||
# ideally this test would just override the respective options
|
# ideally this test would just override the respective options
|
||||||
@modify_settings(
|
@modify_settings(
|
||||||
MIDDLEWARE={'append': 'bookmarks.middlewares.CustomRemoteUserMiddleware'},
|
MIDDLEWARE={"append": "bookmarks.middlewares.CustomRemoteUserMiddleware"},
|
||||||
AUTHENTICATION_BACKENDS={'prepend': 'django.contrib.auth.backends.RemoteUserBackend'}
|
AUTHENTICATION_BACKENDS={
|
||||||
|
"prepend": "django.contrib.auth.backends.RemoteUserBackend"
|
||||||
|
},
|
||||||
)
|
)
|
||||||
def test_auth_proxy_with_custom_header(self):
|
def test_auth_proxy_with_custom_header(self):
|
||||||
with patch.object(CustomRemoteUserMiddleware, 'header', new_callable=PropertyMock) as mock:
|
with patch.object(
|
||||||
mock.return_value = 'Custom-User'
|
CustomRemoteUserMiddleware, "header", new_callable=PropertyMock
|
||||||
user = User.objects.create_user('auth_proxy_user', 'user@example.com', 'password123')
|
) as mock:
|
||||||
|
mock.return_value = "Custom-User"
|
||||||
|
user = User.objects.create_user(
|
||||||
|
"auth_proxy_user", "user@example.com", "password123"
|
||||||
|
)
|
||||||
|
|
||||||
headers = {'Custom-User': user.username}
|
headers = {"Custom-User": user.username}
|
||||||
response = self.client.get(reverse('bookmarks:index'), **headers)
|
response = self.client.get(reverse("bookmarks:index"), **headers)
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
def test_auth_proxy_is_disabled_by_default(self):
|
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}
|
headers = {"REMOTE_USER": user.username}
|
||||||
response = self.client.get(reverse('bookmarks:index'), **headers, follow=True)
|
response = self.client.get(reverse("bookmarks:index"), **headers, follow=True)
|
||||||
|
|
||||||
self.assertRedirects(response, '/login/?next=%2Fbookmarks')
|
self.assertRedirects(response, "/login/?next=%2Fbookmarks")
|
||||||
|
|
|
@ -17,26 +17,37 @@ class BookmarkActionViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
self.assertEqual(len(bookmarks), Bookmark.objects.count())
|
self.assertEqual(len(bookmarks), Bookmark.objects.count())
|
||||||
|
|
||||||
for bookmark in bookmarks:
|
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):
|
def test_archive_should_archive_bookmark(self):
|
||||||
bookmark = self.setup_bookmark()
|
bookmark = self.setup_bookmark()
|
||||||
|
|
||||||
self.client.post(reverse('bookmarks:index.action'), {
|
self.client.post(
|
||||||
'archive': [bookmark.id],
|
reverse("bookmarks:index.action"),
|
||||||
})
|
{
|
||||||
|
"archive": [bookmark.id],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
bookmark.refresh_from_db()
|
bookmark.refresh_from_db()
|
||||||
|
|
||||||
self.assertTrue(bookmark.is_archived)
|
self.assertTrue(bookmark.is_archived)
|
||||||
|
|
||||||
def test_can_only_archive_own_bookmarks(self):
|
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)
|
bookmark = self.setup_bookmark(user=other_user)
|
||||||
|
|
||||||
response = self.client.post(reverse('bookmarks:index.action'), {
|
response = self.client.post(
|
||||||
'archive': [bookmark.id],
|
reverse("bookmarks:index.action"),
|
||||||
})
|
{
|
||||||
|
"archive": [bookmark.id],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
bookmark.refresh_from_db()
|
bookmark.refresh_from_db()
|
||||||
|
|
||||||
|
@ -46,20 +57,28 @@ class BookmarkActionViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
def test_unarchive_should_unarchive_bookmark(self):
|
def test_unarchive_should_unarchive_bookmark(self):
|
||||||
bookmark = self.setup_bookmark(is_archived=True)
|
bookmark = self.setup_bookmark(is_archived=True)
|
||||||
|
|
||||||
self.client.post(reverse('bookmarks:index.action'), {
|
self.client.post(
|
||||||
'unarchive': [bookmark.id],
|
reverse("bookmarks:index.action"),
|
||||||
})
|
{
|
||||||
|
"unarchive": [bookmark.id],
|
||||||
|
},
|
||||||
|
)
|
||||||
bookmark.refresh_from_db()
|
bookmark.refresh_from_db()
|
||||||
|
|
||||||
self.assertFalse(bookmark.is_archived)
|
self.assertFalse(bookmark.is_archived)
|
||||||
|
|
||||||
def test_unarchive_can_only_archive_own_bookmarks(self):
|
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)
|
bookmark = self.setup_bookmark(is_archived=True, user=other_user)
|
||||||
|
|
||||||
response = self.client.post(reverse('bookmarks:index.action'), {
|
response = self.client.post(
|
||||||
'unarchive': [bookmark.id],
|
reverse("bookmarks:index.action"),
|
||||||
})
|
{
|
||||||
|
"unarchive": [bookmark.id],
|
||||||
|
},
|
||||||
|
)
|
||||||
bookmark.refresh_from_db()
|
bookmark.refresh_from_db()
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 404)
|
self.assertEqual(response.status_code, 404)
|
||||||
|
@ -68,28 +87,39 @@ class BookmarkActionViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
def test_delete_should_delete_bookmark(self):
|
def test_delete_should_delete_bookmark(self):
|
||||||
bookmark = self.setup_bookmark()
|
bookmark = self.setup_bookmark()
|
||||||
|
|
||||||
self.client.post(reverse('bookmarks:index.action'), {
|
self.client.post(
|
||||||
'remove': [bookmark.id],
|
reverse("bookmarks:index.action"),
|
||||||
})
|
{
|
||||||
|
"remove": [bookmark.id],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
self.assertEqual(Bookmark.objects.count(), 0)
|
self.assertEqual(Bookmark.objects.count(), 0)
|
||||||
|
|
||||||
def test_delete_can_only_delete_own_bookmarks(self):
|
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)
|
bookmark = self.setup_bookmark(user=other_user)
|
||||||
|
|
||||||
response = self.client.post(reverse('bookmarks:index.action'), {
|
response = self.client.post(
|
||||||
'remove': [bookmark.id],
|
reverse("bookmarks:index.action"),
|
||||||
})
|
{
|
||||||
|
"remove": [bookmark.id],
|
||||||
|
},
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 404)
|
self.assertEqual(response.status_code, 404)
|
||||||
self.assertTrue(Bookmark.objects.filter(id=bookmark.id).exists())
|
self.assertTrue(Bookmark.objects.filter(id=bookmark.id).exists())
|
||||||
|
|
||||||
def test_mark_as_read(self):
|
def test_mark_as_read(self):
|
||||||
bookmark = self.setup_bookmark(unread=True)
|
bookmark = self.setup_bookmark(unread=True)
|
||||||
|
|
||||||
self.client.post(reverse('bookmarks:index.action'), {
|
self.client.post(
|
||||||
'mark_as_read': [bookmark.id],
|
reverse("bookmarks:index.action"),
|
||||||
})
|
{
|
||||||
|
"mark_as_read": [bookmark.id],
|
||||||
|
},
|
||||||
|
)
|
||||||
bookmark.refresh_from_db()
|
bookmark.refresh_from_db()
|
||||||
|
|
||||||
self.assertFalse(bookmark.unread)
|
self.assertFalse(bookmark.unread)
|
||||||
|
@ -97,21 +127,29 @@ class BookmarkActionViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
def test_unshare_should_unshare_bookmark(self):
|
def test_unshare_should_unshare_bookmark(self):
|
||||||
bookmark = self.setup_bookmark(shared=True)
|
bookmark = self.setup_bookmark(shared=True)
|
||||||
|
|
||||||
self.client.post(reverse('bookmarks:index.action'), {
|
self.client.post(
|
||||||
'unshare': [bookmark.id],
|
reverse("bookmarks:index.action"),
|
||||||
})
|
{
|
||||||
|
"unshare": [bookmark.id],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
bookmark.refresh_from_db()
|
bookmark.refresh_from_db()
|
||||||
|
|
||||||
self.assertFalse(bookmark.shared)
|
self.assertFalse(bookmark.shared)
|
||||||
|
|
||||||
def test_can_only_unshare_own_bookmarks(self):
|
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)
|
bookmark = self.setup_bookmark(user=other_user, shared=True)
|
||||||
|
|
||||||
response = self.client.post(reverse('bookmarks:index.action'), {
|
response = self.client.post(
|
||||||
'unshare': [bookmark.id],
|
reverse("bookmarks:index.action"),
|
||||||
})
|
{
|
||||||
|
"unshare": [bookmark.id],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
bookmark.refresh_from_db()
|
bookmark.refresh_from_db()
|
||||||
|
|
||||||
|
@ -123,27 +161,43 @@ class BookmarkActionViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
bookmark2 = self.setup_bookmark()
|
bookmark2 = self.setup_bookmark()
|
||||||
bookmark3 = self.setup_bookmark()
|
bookmark3 = self.setup_bookmark()
|
||||||
|
|
||||||
self.client.post(reverse('bookmarks:index.action'), {
|
self.client.post(
|
||||||
'bulk_action': ['bulk_archive'],
|
reverse("bookmarks:index.action"),
|
||||||
'bulk_execute': [''],
|
{
|
||||||
'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)],
|
"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=bookmark1.id).is_archived)
|
||||||
self.assertTrue(Bookmark.objects.get(id=bookmark2.id).is_archived)
|
self.assertTrue(Bookmark.objects.get(id=bookmark2.id).is_archived)
|
||||||
self.assertTrue(Bookmark.objects.get(id=bookmark3.id).is_archived)
|
self.assertTrue(Bookmark.objects.get(id=bookmark3.id).is_archived)
|
||||||
|
|
||||||
def test_can_only_bulk_archive_own_bookmarks(self):
|
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)
|
bookmark1 = self.setup_bookmark(user=other_user)
|
||||||
bookmark2 = self.setup_bookmark(user=other_user)
|
bookmark2 = self.setup_bookmark(user=other_user)
|
||||||
bookmark3 = self.setup_bookmark(user=other_user)
|
bookmark3 = self.setup_bookmark(user=other_user)
|
||||||
|
|
||||||
self.client.post(reverse('bookmarks:index.action'), {
|
self.client.post(
|
||||||
'bulk_action': ['bulk_archive'],
|
reverse("bookmarks:index.action"),
|
||||||
'bulk_execute': [''],
|
{
|
||||||
'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)],
|
"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=bookmark1.id).is_archived)
|
||||||
self.assertFalse(Bookmark.objects.get(id=bookmark2.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)
|
bookmark2 = self.setup_bookmark(is_archived=True)
|
||||||
bookmark3 = self.setup_bookmark(is_archived=True)
|
bookmark3 = self.setup_bookmark(is_archived=True)
|
||||||
|
|
||||||
self.client.post(reverse('bookmarks:archived.action'), {
|
self.client.post(
|
||||||
'bulk_action': ['bulk_unarchive'],
|
reverse("bookmarks:archived.action"),
|
||||||
'bulk_execute': [''],
|
{
|
||||||
'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)],
|
"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=bookmark1.id).is_archived)
|
||||||
self.assertFalse(Bookmark.objects.get(id=bookmark2.id).is_archived)
|
self.assertFalse(Bookmark.objects.get(id=bookmark2.id).is_archived)
|
||||||
self.assertFalse(Bookmark.objects.get(id=bookmark3.id).is_archived)
|
self.assertFalse(Bookmark.objects.get(id=bookmark3.id).is_archived)
|
||||||
|
|
||||||
def test_can_only_bulk_unarchive_own_bookmarks(self):
|
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)
|
bookmark1 = self.setup_bookmark(is_archived=True, user=other_user)
|
||||||
bookmark2 = 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)
|
bookmark3 = self.setup_bookmark(is_archived=True, user=other_user)
|
||||||
|
|
||||||
self.client.post(reverse('bookmarks:archived.action'), {
|
self.client.post(
|
||||||
'bulk_action': ['bulk_unarchive'],
|
reverse("bookmarks:archived.action"),
|
||||||
'bulk_execute': [''],
|
{
|
||||||
'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)],
|
"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=bookmark1.id).is_archived)
|
||||||
self.assertTrue(Bookmark.objects.get(id=bookmark2.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()
|
bookmark2 = self.setup_bookmark()
|
||||||
bookmark3 = self.setup_bookmark()
|
bookmark3 = self.setup_bookmark()
|
||||||
|
|
||||||
self.client.post(reverse('bookmarks:index.action'), {
|
self.client.post(
|
||||||
'bulk_action': ['bulk_delete'],
|
reverse("bookmarks:index.action"),
|
||||||
'bulk_execute': [''],
|
{
|
||||||
'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)],
|
"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=bookmark1.id).first())
|
||||||
self.assertIsNone(Bookmark.objects.filter(id=bookmark2.id).first())
|
self.assertIsNone(Bookmark.objects.filter(id=bookmark2.id).first())
|
||||||
self.assertIsNone(Bookmark.objects.filter(id=bookmark3.id).first())
|
self.assertIsNone(Bookmark.objects.filter(id=bookmark3.id).first())
|
||||||
|
|
||||||
def test_can_only_bulk_delete_own_bookmarks(self):
|
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)
|
bookmark1 = self.setup_bookmark(user=other_user)
|
||||||
bookmark2 = self.setup_bookmark(user=other_user)
|
bookmark2 = self.setup_bookmark(user=other_user)
|
||||||
bookmark3 = self.setup_bookmark(user=other_user)
|
bookmark3 = self.setup_bookmark(user=other_user)
|
||||||
|
|
||||||
self.client.post(reverse('bookmarks:index.action'), {
|
self.client.post(
|
||||||
'bulk_action': ['bulk_delete'],
|
reverse("bookmarks:index.action"),
|
||||||
'bulk_execute': [''],
|
{
|
||||||
'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)],
|
"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=bookmark1.id).first())
|
||||||
self.assertIsNotNone(Bookmark.objects.filter(id=bookmark2.id).first())
|
self.assertIsNotNone(Bookmark.objects.filter(id=bookmark2.id).first())
|
||||||
|
@ -218,12 +304,19 @@ class BookmarkActionViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
tag1 = self.setup_tag()
|
tag1 = self.setup_tag()
|
||||||
tag2 = self.setup_tag()
|
tag2 = self.setup_tag()
|
||||||
|
|
||||||
self.client.post(reverse('bookmarks:index.action'), {
|
self.client.post(
|
||||||
'bulk_action': ['bulk_tag'],
|
reverse("bookmarks:index.action"),
|
||||||
'bulk_execute': [''],
|
{
|
||||||
'bulk_tag_string': [f'{tag1.name} {tag2.name}'],
|
"bulk_action": ["bulk_tag"],
|
||||||
'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)],
|
"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()
|
bookmark1.refresh_from_db()
|
||||||
bookmark2.refresh_from_db()
|
bookmark2.refresh_from_db()
|
||||||
|
@ -234,19 +327,28 @@ class BookmarkActionViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
self.assertCountEqual(bookmark3.tags.all(), [tag1, tag2])
|
self.assertCountEqual(bookmark3.tags.all(), [tag1, tag2])
|
||||||
|
|
||||||
def test_can_only_bulk_tag_own_bookmarks(self):
|
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)
|
bookmark1 = self.setup_bookmark(user=other_user)
|
||||||
bookmark2 = self.setup_bookmark(user=other_user)
|
bookmark2 = self.setup_bookmark(user=other_user)
|
||||||
bookmark3 = self.setup_bookmark(user=other_user)
|
bookmark3 = self.setup_bookmark(user=other_user)
|
||||||
tag1 = self.setup_tag()
|
tag1 = self.setup_tag()
|
||||||
tag2 = self.setup_tag()
|
tag2 = self.setup_tag()
|
||||||
|
|
||||||
self.client.post(reverse('bookmarks:index.action'), {
|
self.client.post(
|
||||||
'bulk_action': ['bulk_tag'],
|
reverse("bookmarks:index.action"),
|
||||||
'bulk_execute': [''],
|
{
|
||||||
'bulk_tag_string': [f'{tag1.name} {tag2.name}'],
|
"bulk_action": ["bulk_tag"],
|
||||||
'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)],
|
"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()
|
bookmark1.refresh_from_db()
|
||||||
bookmark2.refresh_from_db()
|
bookmark2.refresh_from_db()
|
||||||
|
@ -263,12 +365,19 @@ class BookmarkActionViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
bookmark2 = self.setup_bookmark(tags=[tag1, tag2])
|
bookmark2 = self.setup_bookmark(tags=[tag1, tag2])
|
||||||
bookmark3 = self.setup_bookmark(tags=[tag1, tag2])
|
bookmark3 = self.setup_bookmark(tags=[tag1, tag2])
|
||||||
|
|
||||||
self.client.post(reverse('bookmarks:index.action'), {
|
self.client.post(
|
||||||
'bulk_action': ['bulk_untag'],
|
reverse("bookmarks:index.action"),
|
||||||
'bulk_execute': [''],
|
{
|
||||||
'bulk_tag_string': [f'{tag1.name} {tag2.name}'],
|
"bulk_action": ["bulk_untag"],
|
||||||
'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)],
|
"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()
|
bookmark1.refresh_from_db()
|
||||||
bookmark2.refresh_from_db()
|
bookmark2.refresh_from_db()
|
||||||
|
@ -279,19 +388,28 @@ class BookmarkActionViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
self.assertCountEqual(bookmark3.tags.all(), [])
|
self.assertCountEqual(bookmark3.tags.all(), [])
|
||||||
|
|
||||||
def test_can_only_bulk_untag_own_bookmarks(self):
|
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()
|
tag1 = self.setup_tag()
|
||||||
tag2 = self.setup_tag()
|
tag2 = self.setup_tag()
|
||||||
bookmark1 = self.setup_bookmark(tags=[tag1, tag2], user=other_user)
|
bookmark1 = self.setup_bookmark(tags=[tag1, tag2], user=other_user)
|
||||||
bookmark2 = 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)
|
bookmark3 = self.setup_bookmark(tags=[tag1, tag2], user=other_user)
|
||||||
|
|
||||||
self.client.post(reverse('bookmarks:index.action'), {
|
self.client.post(
|
||||||
'bulk_action': ['bulk_untag'],
|
reverse("bookmarks:index.action"),
|
||||||
'bulk_execute': [''],
|
{
|
||||||
'bulk_tag_string': [f'{tag1.name} {tag2.name}'],
|
"bulk_action": ["bulk_untag"],
|
||||||
'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)],
|
"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()
|
bookmark1.refresh_from_db()
|
||||||
bookmark2.refresh_from_db()
|
bookmark2.refresh_from_db()
|
||||||
|
@ -306,27 +424,43 @@ class BookmarkActionViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
bookmark2 = self.setup_bookmark(unread=True)
|
bookmark2 = self.setup_bookmark(unread=True)
|
||||||
bookmark3 = self.setup_bookmark(unread=True)
|
bookmark3 = self.setup_bookmark(unread=True)
|
||||||
|
|
||||||
self.client.post(reverse('bookmarks:index.action'), {
|
self.client.post(
|
||||||
'bulk_action': ['bulk_read'],
|
reverse("bookmarks:index.action"),
|
||||||
'bulk_execute': [''],
|
{
|
||||||
'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)],
|
"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=bookmark1.id).unread)
|
||||||
self.assertFalse(Bookmark.objects.get(id=bookmark2.id).unread)
|
self.assertFalse(Bookmark.objects.get(id=bookmark2.id).unread)
|
||||||
self.assertFalse(Bookmark.objects.get(id=bookmark3.id).unread)
|
self.assertFalse(Bookmark.objects.get(id=bookmark3.id).unread)
|
||||||
|
|
||||||
def test_can_only_bulk_mark_as_read_own_bookmarks(self):
|
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)
|
bookmark1 = self.setup_bookmark(unread=True, user=other_user)
|
||||||
bookmark2 = 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)
|
bookmark3 = self.setup_bookmark(unread=True, user=other_user)
|
||||||
|
|
||||||
self.client.post(reverse('bookmarks:index.action'), {
|
self.client.post(
|
||||||
'bulk_action': ['bulk_read'],
|
reverse("bookmarks:index.action"),
|
||||||
'bulk_execute': [''],
|
{
|
||||||
'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)],
|
"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=bookmark1.id).unread)
|
||||||
self.assertTrue(Bookmark.objects.get(id=bookmark2.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)
|
bookmark2 = self.setup_bookmark(unread=False)
|
||||||
bookmark3 = self.setup_bookmark(unread=False)
|
bookmark3 = self.setup_bookmark(unread=False)
|
||||||
|
|
||||||
self.client.post(reverse('bookmarks:index.action'), {
|
self.client.post(
|
||||||
'bulk_action': ['bulk_unread'],
|
reverse("bookmarks:index.action"),
|
||||||
'bulk_execute': [''],
|
{
|
||||||
'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)],
|
"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=bookmark1.id).unread)
|
||||||
self.assertTrue(Bookmark.objects.get(id=bookmark2.id).unread)
|
self.assertTrue(Bookmark.objects.get(id=bookmark2.id).unread)
|
||||||
self.assertTrue(Bookmark.objects.get(id=bookmark3.id).unread)
|
self.assertTrue(Bookmark.objects.get(id=bookmark3.id).unread)
|
||||||
|
|
||||||
def test_can_only_bulk_mark_as_unread_own_bookmarks(self):
|
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)
|
bookmark1 = self.setup_bookmark(unread=False, user=other_user)
|
||||||
bookmark2 = 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)
|
bookmark3 = self.setup_bookmark(unread=False, user=other_user)
|
||||||
|
|
||||||
self.client.post(reverse('bookmarks:index.action'), {
|
self.client.post(
|
||||||
'bulk_action': ['bulk_unread'],
|
reverse("bookmarks:index.action"),
|
||||||
'bulk_execute': [''],
|
{
|
||||||
'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)],
|
"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=bookmark1.id).unread)
|
||||||
self.assertFalse(Bookmark.objects.get(id=bookmark2.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)
|
bookmark2 = self.setup_bookmark(shared=False)
|
||||||
bookmark3 = self.setup_bookmark(shared=False)
|
bookmark3 = self.setup_bookmark(shared=False)
|
||||||
|
|
||||||
self.client.post(reverse('bookmarks:index.action'), {
|
self.client.post(
|
||||||
'bulk_action': ['bulk_share'],
|
reverse("bookmarks:index.action"),
|
||||||
'bulk_execute': [''],
|
{
|
||||||
'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)],
|
"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=bookmark1.id).shared)
|
||||||
self.assertTrue(Bookmark.objects.get(id=bookmark2.id).shared)
|
self.assertTrue(Bookmark.objects.get(id=bookmark2.id).shared)
|
||||||
self.assertTrue(Bookmark.objects.get(id=bookmark3.id).shared)
|
self.assertTrue(Bookmark.objects.get(id=bookmark3.id).shared)
|
||||||
|
|
||||||
def test_can_only_bulk_share_own_bookmarks(self):
|
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)
|
bookmark1 = self.setup_bookmark(shared=False, user=other_user)
|
||||||
bookmark2 = 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)
|
bookmark3 = self.setup_bookmark(shared=False, user=other_user)
|
||||||
|
|
||||||
self.client.post(reverse('bookmarks:index.action'), {
|
self.client.post(
|
||||||
'bulk_action': ['bulk_share'],
|
reverse("bookmarks:index.action"),
|
||||||
'bulk_execute': [''],
|
{
|
||||||
'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)],
|
"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=bookmark1.id).shared)
|
||||||
self.assertFalse(Bookmark.objects.get(id=bookmark2.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)
|
bookmark2 = self.setup_bookmark(shared=True)
|
||||||
bookmark3 = self.setup_bookmark(shared=True)
|
bookmark3 = self.setup_bookmark(shared=True)
|
||||||
|
|
||||||
self.client.post(reverse('bookmarks:index.action'), {
|
self.client.post(
|
||||||
'bulk_action': ['bulk_unshare'],
|
reverse("bookmarks:index.action"),
|
||||||
'bulk_execute': [''],
|
{
|
||||||
'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)],
|
"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=bookmark1.id).shared)
|
||||||
self.assertFalse(Bookmark.objects.get(id=bookmark2.id).shared)
|
self.assertFalse(Bookmark.objects.get(id=bookmark2.id).shared)
|
||||||
self.assertFalse(Bookmark.objects.get(id=bookmark3.id).shared)
|
self.assertFalse(Bookmark.objects.get(id=bookmark3.id).shared)
|
||||||
|
|
||||||
def test_can_only_bulk_unshare_own_bookmarks(self):
|
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)
|
bookmark1 = self.setup_bookmark(shared=True, user=other_user)
|
||||||
bookmark2 = 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)
|
bookmark3 = self.setup_bookmark(shared=True, user=other_user)
|
||||||
|
|
||||||
self.client.post(reverse('bookmarks:index.action'), {
|
self.client.post(
|
||||||
'bulk_action': ['bulk_unshare'],
|
reverse("bookmarks:index.action"),
|
||||||
'bulk_execute': [''],
|
{
|
||||||
'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)],
|
"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=bookmark1.id).shared)
|
||||||
self.assertTrue(Bookmark.objects.get(id=bookmark2.id).shared)
|
self.assertTrue(Bookmark.objects.get(id=bookmark2.id).shared)
|
||||||
|
@ -430,11 +612,14 @@ class BookmarkActionViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
bookmark2 = self.setup_bookmark()
|
bookmark2 = self.setup_bookmark()
|
||||||
bookmark3 = self.setup_bookmark()
|
bookmark3 = self.setup_bookmark()
|
||||||
|
|
||||||
self.client.post(reverse('bookmarks:index.action'), {
|
self.client.post(
|
||||||
'bulk_action': ['bulk_archive'],
|
reverse("bookmarks:index.action"),
|
||||||
'bulk_execute': [''],
|
{
|
||||||
'bulk_select_across': ['on'],
|
"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=bookmark1.id).is_archived)
|
||||||
self.assertTrue(Bookmark.objects.get(id=bookmark2.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):
|
def test_bulk_select_across_ignores_page(self):
|
||||||
self.setup_numbered_bookmarks(100)
|
self.setup_numbered_bookmarks(100)
|
||||||
|
|
||||||
self.client.post(reverse('bookmarks:index.action') + '?page=2', {
|
self.client.post(
|
||||||
'bulk_action': ['bulk_delete'],
|
reverse("bookmarks:index.action") + "?page=2",
|
||||||
'bulk_execute': [''],
|
{
|
||||||
'bulk_select_across': ['on'],
|
"bulk_action": ["bulk_delete"],
|
||||||
})
|
"bulk_execute": [""],
|
||||||
|
"bulk_select_across": ["on"],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
self.assertEqual(0, Bookmark.objects.count())
|
self.assertEqual(0, Bookmark.objects.count())
|
||||||
|
|
||||||
|
@ -455,85 +643,108 @@ class BookmarkActionViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
# create a number of bookmarks with different states / visibility
|
# 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)
|
||||||
self.setup_numbered_bookmarks(3, with_tags=True, archived=True)
|
self.setup_numbered_bookmarks(3, with_tags=True, archived=True)
|
||||||
self.setup_numbered_bookmarks(3,
|
self.setup_numbered_bookmarks(
|
||||||
|
3,
|
||||||
shared=True,
|
shared=True,
|
||||||
prefix="Joe's Bookmark",
|
prefix="Joe's Bookmark",
|
||||||
user=self.setup_user(enable_sharing=True))
|
user=self.setup_user(enable_sharing=True),
|
||||||
|
)
|
||||||
|
|
||||||
def test_index_action_bulk_select_across_only_affects_active_bookmarks(self):
|
def test_index_action_bulk_select_across_only_affects_active_bookmarks(self):
|
||||||
self.setup_bulk_edit_scope_test_data()
|
self.setup_bulk_edit_scope_test_data()
|
||||||
|
|
||||||
self.assertIsNotNone(Bookmark.objects.filter(title='Bookmark 1').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 2").first())
|
||||||
self.assertIsNotNone(Bookmark.objects.filter(title='Bookmark 3').first())
|
self.assertIsNotNone(Bookmark.objects.filter(title="Bookmark 3").first())
|
||||||
|
|
||||||
self.client.post(reverse('bookmarks:index.action'), {
|
self.client.post(
|
||||||
'bulk_action': ['bulk_delete'],
|
reverse("bookmarks:index.action"),
|
||||||
'bulk_execute': [''],
|
{
|
||||||
'bulk_select_across': ['on'],
|
"bulk_action": ["bulk_delete"],
|
||||||
})
|
"bulk_execute": [""],
|
||||||
|
"bulk_select_across": ["on"],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
self.assertEqual(6, Bookmark.objects.count())
|
self.assertEqual(6, Bookmark.objects.count())
|
||||||
self.assertIsNone(Bookmark.objects.filter(title='Bookmark 1').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 2").first())
|
||||||
self.assertIsNone(Bookmark.objects.filter(title='Bookmark 3').first())
|
self.assertIsNone(Bookmark.objects.filter(title="Bookmark 3").first())
|
||||||
|
|
||||||
def test_index_action_bulk_select_across_respects_query(self):
|
def test_index_action_bulk_select_across_respects_query(self):
|
||||||
self.setup_numbered_bookmarks(3, prefix='foo')
|
self.setup_numbered_bookmarks(3, prefix="foo")
|
||||||
self.setup_numbered_bookmarks(3, prefix='bar')
|
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', {
|
self.client.post(
|
||||||
'bulk_action': ['bulk_delete'],
|
reverse("bookmarks:index.action") + "?q=foo",
|
||||||
'bulk_execute': [''],
|
{
|
||||||
'bulk_select_across': ['on'],
|
"bulk_action": ["bulk_delete"],
|
||||||
})
|
"bulk_execute": [""],
|
||||||
|
"bulk_select_across": ["on"],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
self.assertEqual(0, Bookmark.objects.filter(title__startswith='foo').count())
|
self.assertEqual(0, Bookmark.objects.filter(title__startswith="foo").count())
|
||||||
self.assertEqual(3, Bookmark.objects.filter(title__startswith='bar').count())
|
self.assertEqual(3, Bookmark.objects.filter(title__startswith="bar").count())
|
||||||
|
|
||||||
def test_archived_action_bulk_select_across_only_affects_archived_bookmarks(self):
|
def test_archived_action_bulk_select_across_only_affects_archived_bookmarks(self):
|
||||||
self.setup_bulk_edit_scope_test_data()
|
self.setup_bulk_edit_scope_test_data()
|
||||||
|
|
||||||
self.assertIsNotNone(Bookmark.objects.filter(title='Archived Bookmark 1').first())
|
self.assertIsNotNone(
|
||||||
self.assertIsNotNone(Bookmark.objects.filter(title='Archived Bookmark 2').first())
|
Bookmark.objects.filter(title="Archived Bookmark 1").first()
|
||||||
self.assertIsNotNone(Bookmark.objects.filter(title='Archived Bookmark 3').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'), {
|
self.client.post(
|
||||||
'bulk_action': ['bulk_delete'],
|
reverse("bookmarks:archived.action"),
|
||||||
'bulk_execute': [''],
|
{
|
||||||
'bulk_select_across': ['on'],
|
"bulk_action": ["bulk_delete"],
|
||||||
})
|
"bulk_execute": [""],
|
||||||
|
"bulk_select_across": ["on"],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
self.assertEqual(6, Bookmark.objects.count())
|
self.assertEqual(6, Bookmark.objects.count())
|
||||||
self.assertIsNone(Bookmark.objects.filter(title='Archived Bookmark 1').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 2").first())
|
||||||
self.assertIsNone(Bookmark.objects.filter(title='Archived Bookmark 3').first())
|
self.assertIsNone(Bookmark.objects.filter(title="Archived Bookmark 3").first())
|
||||||
|
|
||||||
def test_archived_action_bulk_select_across_respects_query(self):
|
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="foo", archived=True)
|
||||||
self.setup_numbered_bookmarks(3, prefix='bar', 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', {
|
self.client.post(
|
||||||
'bulk_action': ['bulk_delete'],
|
reverse("bookmarks:archived.action") + "?q=foo",
|
||||||
'bulk_execute': [''],
|
{
|
||||||
'bulk_select_across': ['on'],
|
"bulk_action": ["bulk_delete"],
|
||||||
})
|
"bulk_execute": [""],
|
||||||
|
"bulk_select_across": ["on"],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
self.assertEqual(0, Bookmark.objects.filter(title__startswith='foo').count())
|
self.assertEqual(0, Bookmark.objects.filter(title__startswith="foo").count())
|
||||||
self.assertEqual(3, Bookmark.objects.filter(title__startswith='bar').count())
|
self.assertEqual(3, Bookmark.objects.filter(title__startswith="bar").count())
|
||||||
|
|
||||||
def test_shared_action_bulk_select_across_not_supported(self):
|
def test_shared_action_bulk_select_across_not_supported(self):
|
||||||
self.setup_bulk_edit_scope_test_data()
|
self.setup_bulk_edit_scope_test_data()
|
||||||
|
|
||||||
response = self.client.post(reverse('bookmarks:shared.action'), {
|
response = self.client.post(
|
||||||
'bulk_action': ['bulk_delete'],
|
reverse("bookmarks:shared.action"),
|
||||||
'bulk_execute': [''],
|
{
|
||||||
'bulk_select_across': ['on'],
|
"bulk_action": ["bulk_delete"],
|
||||||
})
|
"bulk_execute": [""],
|
||||||
|
"bulk_select_across": ["on"],
|
||||||
|
},
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 400)
|
self.assertEqual(response.status_code, 400)
|
||||||
|
|
||||||
def test_handles_empty_bookmark_id(self):
|
def test_handles_empty_bookmark_id(self):
|
||||||
|
@ -541,17 +752,23 @@ class BookmarkActionViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
bookmark2 = self.setup_bookmark()
|
bookmark2 = self.setup_bookmark()
|
||||||
bookmark3 = self.setup_bookmark()
|
bookmark3 = self.setup_bookmark()
|
||||||
|
|
||||||
response = self.client.post(reverse('bookmarks:index.action'), {
|
response = self.client.post(
|
||||||
'bulk_action': ['bulk_archive'],
|
reverse("bookmarks:index.action"),
|
||||||
'bulk_execute': [''],
|
{
|
||||||
})
|
"bulk_action": ["bulk_archive"],
|
||||||
|
"bulk_execute": [""],
|
||||||
|
},
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
|
|
||||||
response = self.client.post(reverse('bookmarks:index.action'), {
|
response = self.client.post(
|
||||||
'bulk_action': ['bulk_archive'],
|
reverse("bookmarks:index.action"),
|
||||||
'bulk_execute': [''],
|
{
|
||||||
'bookmark_id': [],
|
"bulk_action": ["bulk_archive"],
|
||||||
})
|
"bulk_execute": [""],
|
||||||
|
"bookmark_id": [],
|
||||||
|
},
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
|
|
||||||
self.assertBookmarksAreUnmodified([bookmark1, bookmark2, bookmark3])
|
self.assertBookmarksAreUnmodified([bookmark1, bookmark2, bookmark3])
|
||||||
|
@ -561,9 +778,16 @@ class BookmarkActionViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
bookmark2 = self.setup_bookmark()
|
bookmark2 = self.setup_bookmark()
|
||||||
bookmark3 = self.setup_bookmark()
|
bookmark3 = self.setup_bookmark()
|
||||||
|
|
||||||
self.client.post(reverse('bookmarks:index.action'), {
|
self.client.post(
|
||||||
'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)],
|
reverse("bookmarks:index.action"),
|
||||||
})
|
{
|
||||||
|
"bookmark_id": [
|
||||||
|
str(bookmark1.id),
|
||||||
|
str(bookmark2.id),
|
||||||
|
str(bookmark3.id),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
self.assertBookmarksAreUnmodified([bookmark1, bookmark2, bookmark3])
|
self.assertBookmarksAreUnmodified([bookmark1, bookmark2, bookmark3])
|
||||||
|
|
||||||
|
@ -572,14 +796,25 @@ class BookmarkActionViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
bookmark2 = self.setup_bookmark()
|
bookmark2 = self.setup_bookmark()
|
||||||
bookmark3 = self.setup_bookmark()
|
bookmark3 = self.setup_bookmark()
|
||||||
|
|
||||||
url = reverse('bookmarks:index.action') + '?return_url=' + reverse('bookmarks:settings.index')
|
url = (
|
||||||
response = self.client.post(url, {
|
reverse("bookmarks:index.action")
|
||||||
'bulk_action': ['bulk_archive'],
|
+ "?return_url="
|
||||||
'bulk_execute': [''],
|
+ reverse("bookmarks:settings.index")
|
||||||
'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)],
|
)
|
||||||
})
|
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):
|
def test_should_not_redirect_to_external_url(self):
|
||||||
bookmark1 = self.setup_bookmark()
|
bookmark1 = self.setup_bookmark()
|
||||||
|
@ -587,19 +822,27 @@ class BookmarkActionViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
bookmark3 = self.setup_bookmark()
|
bookmark3 = self.setup_bookmark()
|
||||||
|
|
||||||
def post_with(return_url, follow=None):
|
def post_with(return_url, follow=None):
|
||||||
url = reverse('bookmarks:index.action') + f'?return_url={return_url}'
|
url = reverse("bookmarks:index.action") + f"?return_url={return_url}"
|
||||||
return self.client.post(url, {
|
return self.client.post(
|
||||||
'bulk_action': ['bulk_archive'],
|
url,
|
||||||
'bulk_execute': [''],
|
{
|
||||||
'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)],
|
"bulk_action": ["bulk_archive"],
|
||||||
}, follow=follow)
|
"bulk_execute": [""],
|
||||||
|
"bookmark_id": [
|
||||||
|
str(bookmark1.id),
|
||||||
|
str(bookmark2.id),
|
||||||
|
str(bookmark3.id),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
follow=follow,
|
||||||
|
)
|
||||||
|
|
||||||
response = post_with('https://example.com')
|
response = post_with("https://example.com")
|
||||||
self.assertRedirects(response, reverse('bookmarks:index'))
|
self.assertRedirects(response, reverse("bookmarks:index"))
|
||||||
response = post_with('//example.com')
|
response = post_with("//example.com")
|
||||||
self.assertRedirects(response, reverse('bookmarks:index'))
|
self.assertRedirects(response, reverse("bookmarks:index"))
|
||||||
response = post_with('://example.com')
|
response = post_with("://example.com")
|
||||||
self.assertRedirects(response, reverse('bookmarks:index'))
|
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)
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
|
@ -6,7 +6,11 @@ from django.test import TestCase
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from bookmarks.models import Bookmark, BookmarkSearch, Tag, UserProfile
|
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):
|
class BookmarkArchivedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
|
||||||
|
@ -15,33 +19,41 @@ class BookmarkArchivedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
|
||||||
user = self.get_or_create_test_user()
|
user = self.get_or_create_test_user()
|
||||||
self.client.force_login(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())
|
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)
|
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))
|
self.assertEqual(len(bookmark_items), len(bookmarks))
|
||||||
|
|
||||||
for bookmark in bookmarks:
|
for bookmark in bookmarks:
|
||||||
bookmark_item = bookmark_list.select_one(
|
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)
|
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())
|
soup = self.make_soup(response.content.decode())
|
||||||
|
|
||||||
for bookmark in bookmarks:
|
for bookmark in bookmarks:
|
||||||
bookmark_item = soup.select_one(
|
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)
|
self.assertIsNone(bookmark_item)
|
||||||
|
|
||||||
def assertVisibleTags(self, response, tags: List[Tag]):
|
def assertVisibleTags(self, response, tags: List[Tag]):
|
||||||
soup = self.make_soup(response.content.decode())
|
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)
|
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))
|
self.assertEqual(len(tag_items), len(tags))
|
||||||
|
|
||||||
tag_item_names = [tag_item.text.strip() for tag_item in tag_items]
|
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]):
|
def assertInvisibleTags(self, response, tags: List[Tag]):
|
||||||
soup = self.make_soup(response.content.decode())
|
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]
|
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]):
|
def assertSelectedTags(self, response, tags: List[Tag]):
|
||||||
soup = self.make_soup(response.content.decode())
|
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)
|
self.assertIsNotNone(selected_tags)
|
||||||
|
|
||||||
tag_list = selected_tags.select('a')
|
tag_list = selected_tags.select("a")
|
||||||
self.assertEqual(len(tag_list), len(tags))
|
self.assertEqual(len(tag_list), len(tags))
|
||||||
|
|
||||||
for tag in 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):
|
def assertEditLink(self, response, url):
|
||||||
html = response.content.decode()
|
html = response.content.decode()
|
||||||
self.assertInHTML(f'''
|
self.assertInHTML(
|
||||||
|
f"""
|
||||||
<a href="{url}">Edit</a>
|
<a href="{url}">Edit</a>
|
||||||
''', html)
|
""",
|
||||||
|
html,
|
||||||
|
)
|
||||||
|
|
||||||
def assertBulkActionForm(self, response, url: str):
|
def assertBulkActionForm(self, response, url: str):
|
||||||
html = collapse_whitespace(response.content.decode())
|
html = collapse_whitespace(response.content.decode())
|
||||||
needle = collapse_whitespace(f'''
|
needle = collapse_whitespace(
|
||||||
|
f"""
|
||||||
<form class="bookmark-actions"
|
<form class="bookmark-actions"
|
||||||
action="{url}"
|
action="{url}"
|
||||||
method="post" autocomplete="off">
|
method="post" autocomplete="off">
|
||||||
''')
|
"""
|
||||||
|
)
|
||||||
self.assertIn(needle, html)
|
self.assertIn(needle, html)
|
||||||
|
|
||||||
def test_should_list_archived_and_user_owned_bookmarks(self):
|
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)
|
visible_bookmarks = self.setup_numbered_bookmarks(3, archived=True)
|
||||||
invisible_bookmarks = [
|
invisible_bookmarks = [
|
||||||
self.setup_bookmark(is_archived=False),
|
self.setup_bookmark(is_archived=False),
|
||||||
self.setup_bookmark(is_archived=True, user=other_user),
|
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.assertVisibleBookmarks(response, visible_bookmarks)
|
||||||
self.assertInvisibleBookmarks(response, invisible_bookmarks)
|
self.assertInvisibleBookmarks(response, invisible_bookmarks)
|
||||||
|
|
||||||
def test_should_list_bookmarks_matching_query(self):
|
def test_should_list_bookmarks_matching_query(self):
|
||||||
visible_bookmarks = self.setup_numbered_bookmarks(3, prefix='foo', archived=True)
|
visible_bookmarks = self.setup_numbered_bookmarks(
|
||||||
invisible_bookmarks = self.setup_numbered_bookmarks(3, prefix='bar', archived=True)
|
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())
|
html = collapse_whitespace(response.content.decode())
|
||||||
|
|
||||||
self.assertVisibleBookmarks(response, visible_bookmarks)
|
self.assertVisibleBookmarks(response, visible_bookmarks)
|
||||||
self.assertInvisibleBookmarks(response, invisible_bookmarks)
|
self.assertInvisibleBookmarks(response, invisible_bookmarks)
|
||||||
|
|
||||||
def test_should_list_tags_for_archived_and_user_owned_bookmarks(self):
|
def test_should_list_tags_for_archived_and_user_owned_bookmarks(self):
|
||||||
other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123')
|
other_user = User.objects.create_user(
|
||||||
visible_bookmarks = self.setup_numbered_bookmarks(3, with_tags=True, archived=True)
|
"otheruser", "otheruser@example.com", "password123"
|
||||||
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,
|
visible_bookmarks = self.setup_numbered_bookmarks(
|
||||||
tag_prefix='otheruser')
|
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)
|
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.assertVisibleTags(response, visible_tags)
|
||||||
self.assertInvisibleTags(response, invisible_tags)
|
self.assertInvisibleTags(response, invisible_tags)
|
||||||
|
|
||||||
def test_should_list_tags_for_bookmarks_matching_query(self):
|
def test_should_list_tags_for_bookmarks_matching_query(self):
|
||||||
visible_bookmarks = self.setup_numbered_bookmarks(3, with_tags=True, archived=True, prefix='foo',
|
visible_bookmarks = self.setup_numbered_bookmarks(
|
||||||
tag_prefix='foo')
|
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')
|
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)
|
visible_tags = self.get_tags_from_bookmarks(visible_bookmarks)
|
||||||
invisible_tags = self.get_tags_from_bookmarks(invisible_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.assertVisibleTags(response, visible_tags)
|
||||||
self.assertInvisibleTags(response, invisible_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):
|
def test_should_list_bookmarks_and_tags_for_search_preferences(self):
|
||||||
user_profile = self.user.profile
|
user_profile = self.user.profile
|
||||||
user_profile.search_preferences = {
|
user_profile.search_preferences = {
|
||||||
'unread': BookmarkSearch.FILTER_UNREAD_YES,
|
"unread": BookmarkSearch.FILTER_UNREAD_YES,
|
||||||
}
|
}
|
||||||
user_profile.save()
|
user_profile.save()
|
||||||
|
|
||||||
unread_bookmarks = self.setup_numbered_bookmarks(3, archived=True, unread=True, with_tags=True, prefix='unread',
|
unread_bookmarks = self.setup_numbered_bookmarks(
|
||||||
tag_prefix='unread')
|
3,
|
||||||
read_bookmarks = self.setup_numbered_bookmarks(3, archived=True, unread=False, with_tags=True, prefix='read',
|
archived=True,
|
||||||
tag_prefix='read')
|
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)
|
unread_tags = self.get_tags_from_bookmarks(unread_bookmarks)
|
||||||
read_tags = self.get_tags_from_bookmarks(read_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.assertVisibleBookmarks(response, unread_bookmarks)
|
||||||
self.assertInvisibleBookmarks(response, read_bookmarks)
|
self.assertInvisibleBookmarks(response, read_bookmarks)
|
||||||
self.assertVisibleTags(response, unread_tags)
|
self.assertVisibleTags(response, unread_tags)
|
||||||
|
@ -167,11 +216,15 @@ class BookmarkArchivedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
|
||||||
]
|
]
|
||||||
self.setup_bookmark(is_archived=True, tags=tags)
|
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]])
|
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 = [
|
tags = [
|
||||||
self.setup_tag(),
|
self.setup_tag(),
|
||||||
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)
|
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]])
|
self.assertSelectedTags(response, [tags[1]])
|
||||||
|
|
||||||
|
@ -198,16 +254,19 @@ class BookmarkArchivedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
|
||||||
]
|
]
|
||||||
self.setup_bookmark(tags=tags, is_archived=True)
|
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]])
|
self.assertSelectedTags(response, [tags[0], tags[1]])
|
||||||
|
|
||||||
def test_should_open_bookmarks_in_new_page_by_default(self):
|
def test_should_open_bookmarks_in_new_page_by_default(self):
|
||||||
visible_bookmarks = self.setup_numbered_bookmarks(3, archived=True)
|
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):
|
def test_should_open_bookmarks_in_same_page_if_specified_in_user_profile(self):
|
||||||
user = self.get_or_create_test_user()
|
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)
|
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):
|
def test_edit_link_return_url_respects_search_options(self):
|
||||||
bookmark = self.setup_bookmark(title='foo', is_archived=True)
|
bookmark = self.setup_bookmark(title="foo", is_archived=True)
|
||||||
edit_url = reverse('bookmarks:edit', args=[bookmark.id])
|
edit_url = reverse("bookmarks:edit", args=[bookmark.id])
|
||||||
base_url = reverse('bookmarks:archived')
|
base_url = reverse("bookmarks:archived")
|
||||||
|
|
||||||
# without query params
|
# without query params
|
||||||
return_url = urllib.parse.quote(base_url)
|
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)
|
response = self.client.get(base_url)
|
||||||
self.assertEditLink(response, url)
|
self.assertEditLink(response, url)
|
||||||
|
|
||||||
# with query
|
# with query
|
||||||
url_params = '?q=foo'
|
url_params = "?q=foo"
|
||||||
return_url = urllib.parse.quote(base_url + url_params)
|
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)
|
response = self.client.get(base_url + url_params)
|
||||||
self.assertEditLink(response, url)
|
self.assertEditLink(response, url)
|
||||||
|
|
||||||
# with query and sort and page
|
# 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)
|
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)
|
response = self.client.get(base_url + url_params)
|
||||||
self.assertEditLink(response, url)
|
self.assertEditLink(response, url)
|
||||||
|
|
||||||
def test_bulk_edit_respects_search_options(self):
|
def test_bulk_edit_respects_search_options(self):
|
||||||
action_url = reverse('bookmarks:archived.action')
|
action_url = reverse("bookmarks:archived.action")
|
||||||
base_url = reverse('bookmarks:archived')
|
base_url = reverse("bookmarks:archived")
|
||||||
|
|
||||||
# without params
|
# without params
|
||||||
return_url = urllib.parse.quote_plus(base_url)
|
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)
|
response = self.client.get(base_url)
|
||||||
self.assertBulkActionForm(response, url)
|
self.assertBulkActionForm(response, url)
|
||||||
|
|
||||||
# with query
|
# with query
|
||||||
url_params = '?q=foo'
|
url_params = "?q=foo"
|
||||||
return_url = urllib.parse.quote_plus(base_url + url_params)
|
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)
|
response = self.client.get(base_url + url_params)
|
||||||
self.assertBulkActionForm(response, url)
|
self.assertBulkActionForm(response, url)
|
||||||
|
|
||||||
# with query and sort
|
# 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)
|
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)
|
response = self.client.get(base_url + url_params)
|
||||||
self.assertBulkActionForm(response, url)
|
self.assertBulkActionForm(response, url)
|
||||||
|
|
||||||
def test_allowed_bulk_actions(self):
|
def test_allowed_bulk_actions(self):
|
||||||
url = reverse('bookmarks:archived')
|
url = reverse("bookmarks:archived")
|
||||||
response = self.client.get(url)
|
response = self.client.get(url)
|
||||||
html = response.content.decode()
|
html = response.content.decode()
|
||||||
|
|
||||||
self.assertInHTML(f'''
|
self.assertInHTML(
|
||||||
|
f"""
|
||||||
<select name="bulk_action" class="form-select select-sm">
|
<select name="bulk_action" class="form-select select-sm">
|
||||||
<option value="bulk_unarchive">Unarchive</option>
|
<option value="bulk_unarchive">Unarchive</option>
|
||||||
<option value="bulk_delete">Delete</option>
|
<option value="bulk_delete">Delete</option>
|
||||||
|
@ -289,18 +349,21 @@ class BookmarkArchivedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
|
||||||
<option value="bulk_read">Mark as read</option>
|
<option value="bulk_read">Mark as read</option>
|
||||||
<option value="bulk_unread">Mark as unread</option>
|
<option value="bulk_unread">Mark as unread</option>
|
||||||
</select>
|
</select>
|
||||||
''', html)
|
""",
|
||||||
|
html,
|
||||||
|
)
|
||||||
|
|
||||||
def test_allowed_bulk_actions_with_sharing_enabled(self):
|
def test_allowed_bulk_actions_with_sharing_enabled(self):
|
||||||
user_profile = self.user.profile
|
user_profile = self.user.profile
|
||||||
user_profile.enable_sharing = True
|
user_profile.enable_sharing = True
|
||||||
user_profile.save()
|
user_profile.save()
|
||||||
|
|
||||||
url = reverse('bookmarks:archived')
|
url = reverse("bookmarks:archived")
|
||||||
response = self.client.get(url)
|
response = self.client.get(url)
|
||||||
html = response.content.decode()
|
html = response.content.decode()
|
||||||
|
|
||||||
self.assertInHTML(f'''
|
self.assertInHTML(
|
||||||
|
f"""
|
||||||
<select name="bulk_action" class="form-select select-sm">
|
<select name="bulk_action" class="form-select select-sm">
|
||||||
<option value="bulk_unarchive">Unarchive</option>
|
<option value="bulk_unarchive">Unarchive</option>
|
||||||
<option value="bulk_delete">Delete</option>
|
<option value="bulk_delete">Delete</option>
|
||||||
|
@ -311,142 +374,191 @@ class BookmarkArchivedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
|
||||||
<option value="bulk_share">Share</option>
|
<option value="bulk_share">Share</option>
|
||||||
<option value="bulk_unshare">Unshare</option>
|
<option value="bulk_unshare">Unshare</option>
|
||||||
</select>
|
</select>
|
||||||
''', html)
|
""",
|
||||||
|
html,
|
||||||
|
)
|
||||||
|
|
||||||
def test_apply_search_preferences(self):
|
def test_apply_search_preferences(self):
|
||||||
# no params
|
# 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.status_code, 302)
|
||||||
self.assertEqual(response.url, reverse('bookmarks:archived'))
|
self.assertEqual(response.url, reverse("bookmarks:archived"))
|
||||||
|
|
||||||
# some params
|
# some params
|
||||||
response = self.client.post(reverse('bookmarks:archived'), {
|
response = self.client.post(
|
||||||
'q': 'foo',
|
reverse("bookmarks:archived"),
|
||||||
'sort': BookmarkSearch.SORT_TITLE_ASC,
|
{
|
||||||
})
|
"q": "foo",
|
||||||
|
"sort": BookmarkSearch.SORT_TITLE_ASC,
|
||||||
|
},
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 302)
|
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
|
# params with default value are removed
|
||||||
response = self.client.post(reverse('bookmarks:archived'), {
|
response = self.client.post(
|
||||||
'q': 'foo',
|
reverse("bookmarks:archived"),
|
||||||
'user': '',
|
{
|
||||||
'sort': BookmarkSearch.SORT_ADDED_DESC,
|
"q": "foo",
|
||||||
'shared': BookmarkSearch.FILTER_SHARED_OFF,
|
"user": "",
|
||||||
'unread': BookmarkSearch.FILTER_UNREAD_YES,
|
"sort": BookmarkSearch.SORT_ADDED_DESC,
|
||||||
})
|
"shared": BookmarkSearch.FILTER_SHARED_OFF,
|
||||||
|
"unread": BookmarkSearch.FILTER_UNREAD_YES,
|
||||||
|
},
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 302)
|
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
|
# page is removed
|
||||||
response = self.client.post(reverse('bookmarks:archived'), {
|
response = self.client.post(
|
||||||
'q': 'foo',
|
reverse("bookmarks:archived"),
|
||||||
'page': '2',
|
{
|
||||||
'sort': BookmarkSearch.SORT_TITLE_ASC,
|
"q": "foo",
|
||||||
})
|
"page": "2",
|
||||||
|
"sort": BookmarkSearch.SORT_TITLE_ASC,
|
||||||
|
},
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 302)
|
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):
|
def test_save_search_preferences(self):
|
||||||
user_profile = self.user.profile
|
user_profile = self.user.profile
|
||||||
|
|
||||||
# no params
|
# no params
|
||||||
self.client.post(reverse('bookmarks:archived'), {
|
self.client.post(
|
||||||
'save': '',
|
reverse("bookmarks:archived"),
|
||||||
})
|
{
|
||||||
|
"save": "",
|
||||||
|
},
|
||||||
|
)
|
||||||
user_profile.refresh_from_db()
|
user_profile.refresh_from_db()
|
||||||
self.assertEqual(user_profile.search_preferences, {
|
self.assertEqual(
|
||||||
'sort': BookmarkSearch.SORT_ADDED_DESC,
|
user_profile.search_preferences,
|
||||||
'shared': BookmarkSearch.FILTER_SHARED_OFF,
|
{
|
||||||
'unread': BookmarkSearch.FILTER_UNREAD_OFF,
|
"sort": BookmarkSearch.SORT_ADDED_DESC,
|
||||||
})
|
"shared": BookmarkSearch.FILTER_SHARED_OFF,
|
||||||
|
"unread": BookmarkSearch.FILTER_UNREAD_OFF,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
# with param
|
# with param
|
||||||
self.client.post(reverse('bookmarks:archived'), {
|
self.client.post(
|
||||||
'save': '',
|
reverse("bookmarks:archived"),
|
||||||
'sort': BookmarkSearch.SORT_TITLE_ASC,
|
{
|
||||||
})
|
"save": "",
|
||||||
|
"sort": BookmarkSearch.SORT_TITLE_ASC,
|
||||||
|
},
|
||||||
|
)
|
||||||
user_profile.refresh_from_db()
|
user_profile.refresh_from_db()
|
||||||
self.assertEqual(user_profile.search_preferences, {
|
self.assertEqual(
|
||||||
'sort': BookmarkSearch.SORT_TITLE_ASC,
|
user_profile.search_preferences,
|
||||||
'shared': BookmarkSearch.FILTER_SHARED_OFF,
|
{
|
||||||
'unread': BookmarkSearch.FILTER_UNREAD_OFF,
|
"sort": BookmarkSearch.SORT_TITLE_ASC,
|
||||||
})
|
"shared": BookmarkSearch.FILTER_SHARED_OFF,
|
||||||
|
"unread": BookmarkSearch.FILTER_UNREAD_OFF,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
# add a param
|
# add a param
|
||||||
self.client.post(reverse('bookmarks:archived'), {
|
self.client.post(
|
||||||
'save': '',
|
reverse("bookmarks:archived"),
|
||||||
'sort': BookmarkSearch.SORT_TITLE_ASC,
|
{
|
||||||
'unread': BookmarkSearch.FILTER_UNREAD_YES,
|
"save": "",
|
||||||
})
|
"sort": BookmarkSearch.SORT_TITLE_ASC,
|
||||||
|
"unread": BookmarkSearch.FILTER_UNREAD_YES,
|
||||||
|
},
|
||||||
|
)
|
||||||
user_profile.refresh_from_db()
|
user_profile.refresh_from_db()
|
||||||
self.assertEqual(user_profile.search_preferences, {
|
self.assertEqual(
|
||||||
'sort': BookmarkSearch.SORT_TITLE_ASC,
|
user_profile.search_preferences,
|
||||||
'shared': BookmarkSearch.FILTER_SHARED_OFF,
|
{
|
||||||
'unread': BookmarkSearch.FILTER_UNREAD_YES,
|
"sort": BookmarkSearch.SORT_TITLE_ASC,
|
||||||
})
|
"shared": BookmarkSearch.FILTER_SHARED_OFF,
|
||||||
|
"unread": BookmarkSearch.FILTER_UNREAD_YES,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
# remove a param
|
# remove a param
|
||||||
self.client.post(reverse('bookmarks:archived'), {
|
self.client.post(
|
||||||
'save': '',
|
reverse("bookmarks:archived"),
|
||||||
'unread': BookmarkSearch.FILTER_UNREAD_YES,
|
{
|
||||||
})
|
"save": "",
|
||||||
|
"unread": BookmarkSearch.FILTER_UNREAD_YES,
|
||||||
|
},
|
||||||
|
)
|
||||||
user_profile.refresh_from_db()
|
user_profile.refresh_from_db()
|
||||||
self.assertEqual(user_profile.search_preferences, {
|
self.assertEqual(
|
||||||
'sort': BookmarkSearch.SORT_ADDED_DESC,
|
user_profile.search_preferences,
|
||||||
'shared': BookmarkSearch.FILTER_SHARED_OFF,
|
{
|
||||||
'unread': BookmarkSearch.FILTER_UNREAD_YES,
|
"sort": BookmarkSearch.SORT_ADDED_DESC,
|
||||||
})
|
"shared": BookmarkSearch.FILTER_SHARED_OFF,
|
||||||
|
"unread": BookmarkSearch.FILTER_UNREAD_YES,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
# ignores non-preferences
|
# ignores non-preferences
|
||||||
self.client.post(reverse('bookmarks:archived'), {
|
self.client.post(
|
||||||
'save': '',
|
reverse("bookmarks:archived"),
|
||||||
'q': 'foo',
|
{
|
||||||
'user': 'john',
|
"save": "",
|
||||||
'page': '3',
|
"q": "foo",
|
||||||
'sort': BookmarkSearch.SORT_TITLE_ASC,
|
"user": "john",
|
||||||
})
|
"page": "3",
|
||||||
|
"sort": BookmarkSearch.SORT_TITLE_ASC,
|
||||||
|
},
|
||||||
|
)
|
||||||
user_profile.refresh_from_db()
|
user_profile.refresh_from_db()
|
||||||
self.assertEqual(user_profile.search_preferences, {
|
self.assertEqual(
|
||||||
'sort': BookmarkSearch.SORT_TITLE_ASC,
|
user_profile.search_preferences,
|
||||||
'shared': BookmarkSearch.FILTER_SHARED_OFF,
|
{
|
||||||
'unread': BookmarkSearch.FILTER_UNREAD_OFF,
|
"sort": BookmarkSearch.SORT_TITLE_ASC,
|
||||||
})
|
"shared": BookmarkSearch.FILTER_SHARED_OFF,
|
||||||
|
"unread": BookmarkSearch.FILTER_UNREAD_OFF,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
def test_url_encode_bookmark_actions_url(self):
|
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)
|
response = self.client.get(url)
|
||||||
html = response.content.decode()
|
html = response.content.decode()
|
||||||
soup = self.make_soup(html)
|
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'],
|
self.assertEqual(
|
||||||
'/bookmarks/archived/action?q=%23foo&return_url=%2Fbookmarks%2Farchived%3Fq%3D%2523foo')
|
actions_form.attrs["action"],
|
||||||
|
"/bookmarks/archived/action?q=%23foo&return_url=%2Fbookmarks%2Farchived%3Fq%3D%2523foo",
|
||||||
|
)
|
||||||
|
|
||||||
def test_encode_search_params(self):
|
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)
|
response = self.client.get(url)
|
||||||
self.assertNotContains(response, 'alert(\'xss\')')
|
self.assertNotContains(response, "alert('xss')")
|
||||||
self.assertContains(response, bookmark.url)
|
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)
|
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)
|
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)
|
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)
|
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)
|
response = self.client.get(url)
|
||||||
self.assertNotContains(response, 'alert(\'xss\')')
|
self.assertNotContains(response, "alert('xss')")
|
||||||
|
|
|
@ -8,7 +8,9 @@ from django.db.utils import DEFAULT_DB_ALIAS
|
||||||
from bookmarks.tests.helpers import BookmarkFactoryMixin
|
from bookmarks.tests.helpers import BookmarkFactoryMixin
|
||||||
|
|
||||||
|
|
||||||
class BookmarkArchivedViewPerformanceTestCase(TransactionTestCase, BookmarkFactoryMixin):
|
class BookmarkArchivedViewPerformanceTestCase(
|
||||||
|
TransactionTestCase, BookmarkFactoryMixin
|
||||||
|
):
|
||||||
|
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
user = self.get_or_create_test_user()
|
user = self.get_or_create_test_user()
|
||||||
|
@ -26,8 +28,10 @@ class BookmarkArchivedViewPerformanceTestCase(TransactionTestCase, BookmarkFacto
|
||||||
# capture number of queries
|
# capture number of queries
|
||||||
context = CaptureQueriesContext(self.get_connection())
|
context = CaptureQueriesContext(self.get_connection())
|
||||||
with context:
|
with context:
|
||||||
response = self.client.get(reverse('bookmarks:archived'))
|
response = self.client.get(reverse("bookmarks:archived"))
|
||||||
self.assertContains(response, '<li ld-bookmark-item>', num_initial_bookmarks)
|
self.assertContains(
|
||||||
|
response, "<li ld-bookmark-item>", num_initial_bookmarks
|
||||||
|
)
|
||||||
|
|
||||||
number_of_queries = context.final_queries
|
number_of_queries = context.final_queries
|
||||||
|
|
||||||
|
@ -38,5 +42,9 @@ class BookmarkArchivedViewPerformanceTestCase(TransactionTestCase, BookmarkFacto
|
||||||
|
|
||||||
# assert num queries doesn't increase
|
# assert num queries doesn't increase
|
||||||
with self.assertNumQueries(number_of_queries):
|
with self.assertNumQueries(number_of_queries):
|
||||||
response = self.client.get(reverse('bookmarks:archived'))
|
response = self.client.get(reverse("bookmarks:archived"))
|
||||||
self.assertContains(response, '<li ld-bookmark-item>', num_initial_bookmarks + num_additional_bookmarks)
|
self.assertContains(
|
||||||
|
response,
|
||||||
|
"<li ld-bookmark-item>",
|
||||||
|
num_initial_bookmarks + num_additional_bookmarks,
|
||||||
|
)
|
||||||
|
|
|
@ -16,153 +16,192 @@ class BookmarkEditViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
if overrides is None:
|
if overrides is None:
|
||||||
overrides = {}
|
overrides = {}
|
||||||
form_data = {
|
form_data = {
|
||||||
'url': 'http://example.com/edited',
|
"url": "http://example.com/edited",
|
||||||
'tag_string': 'editedtag1 editedtag2',
|
"tag_string": "editedtag1 editedtag2",
|
||||||
'title': 'edited title',
|
"title": "edited title",
|
||||||
'description': 'edited description',
|
"description": "edited description",
|
||||||
'notes': 'edited notes',
|
"notes": "edited notes",
|
||||||
'unread': False,
|
"unread": False,
|
||||||
'shared': False,
|
"shared": False,
|
||||||
}
|
}
|
||||||
return {**form_data, **overrides}
|
return {**form_data, **overrides}
|
||||||
|
|
||||||
def test_should_edit_bookmark(self):
|
def test_should_edit_bookmark(self):
|
||||||
bookmark = self.setup_bookmark()
|
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()
|
bookmark.refresh_from_db()
|
||||||
|
|
||||||
self.assertEqual(bookmark.owner, self.user)
|
self.assertEqual(bookmark.owner, self.user)
|
||||||
self.assertEqual(bookmark.url, form_data['url'])
|
self.assertEqual(bookmark.url, form_data["url"])
|
||||||
self.assertEqual(bookmark.title, form_data['title'])
|
self.assertEqual(bookmark.title, form_data["title"])
|
||||||
self.assertEqual(bookmark.description, form_data['description'])
|
self.assertEqual(bookmark.description, form_data["description"])
|
||||||
self.assertEqual(bookmark.notes, form_data['notes'])
|
self.assertEqual(bookmark.notes, form_data["notes"])
|
||||||
self.assertEqual(bookmark.unread, form_data['unread'])
|
self.assertEqual(bookmark.unread, form_data["unread"])
|
||||||
self.assertEqual(bookmark.shared, form_data['shared'])
|
self.assertEqual(bookmark.shared, form_data["shared"])
|
||||||
self.assertEqual(bookmark.tags.count(), 2)
|
self.assertEqual(bookmark.tags.count(), 2)
|
||||||
tags = bookmark.tags.order_by('name').all()
|
tags = bookmark.tags.order_by("name").all()
|
||||||
self.assertEqual(tags[0].name, 'editedtag1')
|
self.assertEqual(tags[0].name, "editedtag1")
|
||||||
self.assertEqual(tags[1].name, 'editedtag2')
|
self.assertEqual(tags[1].name, "editedtag2")
|
||||||
|
|
||||||
def test_should_edit_unread_state(self):
|
def test_should_edit_unread_state(self):
|
||||||
bookmark = self.setup_bookmark()
|
bookmark = self.setup_bookmark()
|
||||||
|
|
||||||
form_data = self.create_form_data({'id': bookmark.id, 'unread': True})
|
form_data = self.create_form_data({"id": bookmark.id, "unread": True})
|
||||||
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()
|
bookmark.refresh_from_db()
|
||||||
self.assertTrue(bookmark.unread)
|
self.assertTrue(bookmark.unread)
|
||||||
|
|
||||||
form_data = self.create_form_data({'id': bookmark.id, 'unread': False})
|
form_data = self.create_form_data({"id": bookmark.id, "unread": False})
|
||||||
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()
|
bookmark.refresh_from_db()
|
||||||
self.assertFalse(bookmark.unread)
|
self.assertFalse(bookmark.unread)
|
||||||
|
|
||||||
def test_should_edit_shared_state(self):
|
def test_should_edit_shared_state(self):
|
||||||
bookmark = self.setup_bookmark()
|
bookmark = self.setup_bookmark()
|
||||||
|
|
||||||
form_data = self.create_form_data({'id': bookmark.id, 'shared': True})
|
form_data = self.create_form_data({"id": bookmark.id, "shared": True})
|
||||||
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()
|
bookmark.refresh_from_db()
|
||||||
self.assertTrue(bookmark.shared)
|
self.assertTrue(bookmark.shared)
|
||||||
|
|
||||||
form_data = self.create_form_data({'id': bookmark.id, 'shared': False})
|
form_data = self.create_form_data({"id": bookmark.id, "shared": False})
|
||||||
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()
|
bookmark.refresh_from_db()
|
||||||
self.assertFalse(bookmark.shared)
|
self.assertFalse(bookmark.shared)
|
||||||
|
|
||||||
def test_should_prefill_bookmark_form_fields(self):
|
def test_should_prefill_bookmark_form_fields(self):
|
||||||
tag1 = self.setup_tag()
|
tag1 = self.setup_tag()
|
||||||
tag2 = self.setup_tag()
|
tag2 = self.setup_tag()
|
||||||
bookmark = self.setup_bookmark(tags=[tag1, tag2], title='edited title', description='edited description',
|
bookmark = self.setup_bookmark(
|
||||||
notes='edited notes', website_title='website title',
|
tags=[tag1, tag2],
|
||||||
website_description='website description')
|
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()
|
html = response.content.decode()
|
||||||
|
|
||||||
self.assertInHTML(f'''
|
self.assertInHTML(
|
||||||
|
f"""
|
||||||
<input type="text" name="url" value="{bookmark.url}" placeholder=" "
|
<input type="text" name="url" value="{bookmark.url}" placeholder=" "
|
||||||
autofocus class="form-input" required id="id_url">
|
autofocus class="form-input" required id="id_url">
|
||||||
''', html)
|
""",
|
||||||
|
html,
|
||||||
|
)
|
||||||
|
|
||||||
tag_string = build_tag_string(bookmark.tag_names, ' ')
|
tag_string = build_tag_string(bookmark.tag_names, " ")
|
||||||
self.assertInHTML(f'''
|
self.assertInHTML(
|
||||||
|
f"""
|
||||||
<input ld-tag-autocomplete type="text" name="tag_string" value="{tag_string}"
|
<input ld-tag-autocomplete type="text" name="tag_string" value="{tag_string}"
|
||||||
autocomplete="off" autocapitalize="off" class="form-input" id="id_tag_string">
|
autocomplete="off" autocapitalize="off" class="form-input" id="id_tag_string">
|
||||||
''', html)
|
""",
|
||||||
|
html,
|
||||||
|
)
|
||||||
|
|
||||||
self.assertInHTML(f'''
|
self.assertInHTML(
|
||||||
|
f"""
|
||||||
<input type="text" name="title" value="{bookmark.title}" maxlength="512" autocomplete="off"
|
<input type="text" name="title" value="{bookmark.title}" maxlength="512" autocomplete="off"
|
||||||
class="form-input" id="id_title">
|
class="form-input" id="id_title">
|
||||||
''', html)
|
""",
|
||||||
|
html,
|
||||||
|
)
|
||||||
|
|
||||||
self.assertInHTML(f'''
|
self.assertInHTML(
|
||||||
|
f"""
|
||||||
<textarea name="description" cols="40" rows="2" class="form-input" id="id_description">
|
<textarea name="description" cols="40" rows="2" class="form-input" id="id_description">
|
||||||
{bookmark.description}
|
{bookmark.description}
|
||||||
</textarea>
|
</textarea>
|
||||||
''', html)
|
""",
|
||||||
|
html,
|
||||||
|
)
|
||||||
|
|
||||||
self.assertInHTML(f'''
|
self.assertInHTML(
|
||||||
|
f"""
|
||||||
<textarea name="notes" cols="40" rows="8" class="form-input" id="id_notes">
|
<textarea name="notes" cols="40" rows="8" class="form-input" id="id_notes">
|
||||||
{bookmark.notes}
|
{bookmark.notes}
|
||||||
</textarea>
|
</textarea>
|
||||||
''', html)
|
""",
|
||||||
|
html,
|
||||||
|
)
|
||||||
|
|
||||||
self.assertInHTML(f'''
|
self.assertInHTML(
|
||||||
|
f"""
|
||||||
<input type="hidden" name="website_title" id="id_website_title"
|
<input type="hidden" name="website_title" id="id_website_title"
|
||||||
value="{bookmark.website_title}">
|
value="{bookmark.website_title}">
|
||||||
''', html)
|
""",
|
||||||
|
html,
|
||||||
|
)
|
||||||
|
|
||||||
self.assertInHTML(f'''
|
self.assertInHTML(
|
||||||
|
f"""
|
||||||
<input type="hidden" name="website_description" id="id_website_description"
|
<input type="hidden" name="website_description" id="id_website_description"
|
||||||
value="{bookmark.website_description}">
|
value="{bookmark.website_description}">
|
||||||
''', html)
|
""",
|
||||||
|
html,
|
||||||
|
)
|
||||||
|
|
||||||
def test_should_redirect_to_return_url(self):
|
def test_should_redirect_to_return_url(self):
|
||||||
bookmark = self.setup_bookmark()
|
bookmark = self.setup_bookmark()
|
||||||
form_data = self.create_form_data()
|
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)
|
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):
|
def test_should_redirect_to_bookmark_index_by_default(self):
|
||||||
bookmark = self.setup_bookmark()
|
bookmark = self.setup_bookmark()
|
||||||
form_data = self.create_form_data()
|
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):
|
def test_should_not_redirect_to_external_url(self):
|
||||||
bookmark = self.setup_bookmark()
|
bookmark = self.setup_bookmark()
|
||||||
|
|
||||||
def post_with(return_url, follow=None):
|
def post_with(return_url, follow=None):
|
||||||
form_data = self.create_form_data()
|
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)
|
return self.client.post(url, form_data, follow=follow)
|
||||||
|
|
||||||
response = post_with('https://example.com')
|
response = post_with("https://example.com")
|
||||||
self.assertRedirects(response, reverse('bookmarks:index'))
|
self.assertRedirects(response, reverse("bookmarks:index"))
|
||||||
response = post_with('//example.com')
|
response = post_with("//example.com")
|
||||||
self.assertRedirects(response, reverse('bookmarks:index'))
|
self.assertRedirects(response, reverse("bookmarks:index"))
|
||||||
response = post_with('://example.com')
|
response = post_with("://example.com")
|
||||||
self.assertRedirects(response, reverse('bookmarks:index'))
|
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)
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
def test_can_only_edit_own_bookmarks(self):
|
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)
|
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()
|
bookmark.refresh_from_db()
|
||||||
self.assertNotEqual(bookmark.url, form_data['url'])
|
self.assertNotEqual(bookmark.url, form_data["url"])
|
||||||
self.assertEqual(response.status_code, 404)
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
def test_should_respect_share_profile_setting(self):
|
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.enable_sharing = False
|
||||||
self.user.profile.save()
|
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()
|
html = response.content.decode()
|
||||||
|
|
||||||
self.assertInHTML('''
|
self.assertInHTML(
|
||||||
|
"""
|
||||||
<label for="id_shared" class="form-checkbox">
|
<label for="id_shared" class="form-checkbox">
|
||||||
<input type="checkbox" name="shared" id="id_shared">
|
<input type="checkbox" name="shared" id="id_shared">
|
||||||
<i class="form-icon"></i>
|
<i class="form-icon"></i>
|
||||||
<span>Share</span>
|
<span>Share</span>
|
||||||
</label>
|
</label>
|
||||||
''', html, count=0)
|
""",
|
||||||
|
html,
|
||||||
|
count=0,
|
||||||
|
)
|
||||||
|
|
||||||
self.user.profile.enable_sharing = True
|
self.user.profile.enable_sharing = True
|
||||||
self.user.profile.save()
|
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()
|
html = response.content.decode()
|
||||||
|
|
||||||
self.assertInHTML('''
|
self.assertInHTML(
|
||||||
|
"""
|
||||||
<label for="id_shared" class="form-checkbox">
|
<label for="id_shared" class="form-checkbox">
|
||||||
<input type="checkbox" name="shared" id="id_shared">
|
<input type="checkbox" name="shared" id="id_shared">
|
||||||
<i class="form-icon"></i>
|
<i class="form-icon"></i>
|
||||||
<span>Share</span>
|
<span>Share</span>
|
||||||
</label>
|
</label>
|
||||||
''', html, count=1)
|
""",
|
||||||
|
html,
|
||||||
|
count=1,
|
||||||
|
)
|
||||||
|
|
||||||
def test_should_hide_notes_if_there_are_no_notes(self):
|
def test_should_hide_notes_if_there_are_no_notes(self):
|
||||||
bookmark = self.setup_bookmark()
|
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, '<details class="notes">', count=1)
|
self.assertContains(response, '<details class="notes">', count=1)
|
||||||
|
|
||||||
def test_should_show_notes_if_there_are_notes(self):
|
def test_should_show_notes_if_there_are_notes(self):
|
||||||
bookmark = self.setup_bookmark(notes='test notes')
|
bookmark = self.setup_bookmark(notes="test notes")
|
||||||
response = self.client.get(reverse('bookmarks:edit', args=[bookmark.id]))
|
response = self.client.get(reverse("bookmarks:edit", args=[bookmark.id]))
|
||||||
|
|
||||||
self.assertContains(response, '<details class="notes" open>', count=1)
|
self.assertContains(response, '<details class="notes" open>', count=1)
|
||||||
|
|
|
@ -6,7 +6,11 @@ from django.test import TestCase
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from bookmarks.models import Bookmark, BookmarkSearch, Tag, UserProfile
|
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):
|
class BookmarkIndexViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
|
||||||
|
@ -15,33 +19,41 @@ class BookmarkIndexViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
|
||||||
user = self.get_or_create_test_user()
|
user = self.get_or_create_test_user()
|
||||||
self.client.force_login(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())
|
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)
|
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))
|
self.assertEqual(len(bookmark_items), len(bookmarks))
|
||||||
|
|
||||||
for bookmark in bookmarks:
|
for bookmark in bookmarks:
|
||||||
bookmark_item = bookmark_list.select_one(
|
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)
|
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())
|
soup = self.make_soup(response.content.decode())
|
||||||
|
|
||||||
for bookmark in bookmarks:
|
for bookmark in bookmarks:
|
||||||
bookmark_item = soup.select_one(
|
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)
|
self.assertIsNone(bookmark_item)
|
||||||
|
|
||||||
def assertVisibleTags(self, response, tags: List[Tag]):
|
def assertVisibleTags(self, response, tags: List[Tag]):
|
||||||
soup = self.make_soup(response.content.decode())
|
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)
|
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))
|
self.assertEqual(len(tag_items), len(tags))
|
||||||
|
|
||||||
tag_item_names = [tag_item.text.strip() for tag_item in tag_items]
|
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]):
|
def assertInvisibleTags(self, response, tags: List[Tag]):
|
||||||
soup = self.make_soup(response.content.decode())
|
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]
|
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]):
|
def assertSelectedTags(self, response, tags: List[Tag]):
|
||||||
soup = self.make_soup(response.content.decode())
|
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)
|
self.assertIsNotNone(selected_tags)
|
||||||
|
|
||||||
tag_list = selected_tags.select('a')
|
tag_list = selected_tags.select("a")
|
||||||
self.assertEqual(len(tag_list), len(tags))
|
self.assertEqual(len(tag_list), len(tags))
|
||||||
|
|
||||||
for tag in 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):
|
def assertEditLink(self, response, url):
|
||||||
html = response.content.decode()
|
html = response.content.decode()
|
||||||
self.assertInHTML(f'''
|
self.assertInHTML(
|
||||||
|
f"""
|
||||||
<a href="{url}">Edit</a>
|
<a href="{url}">Edit</a>
|
||||||
''', html)
|
""",
|
||||||
|
html,
|
||||||
|
)
|
||||||
|
|
||||||
def assertBulkActionForm(self, response, url: str):
|
def assertBulkActionForm(self, response, url: str):
|
||||||
html = collapse_whitespace(response.content.decode())
|
html = collapse_whitespace(response.content.decode())
|
||||||
needle = collapse_whitespace(f'''
|
needle = collapse_whitespace(
|
||||||
|
f"""
|
||||||
<form class="bookmark-actions"
|
<form class="bookmark-actions"
|
||||||
action="{url}"
|
action="{url}"
|
||||||
method="post" autocomplete="off">
|
method="post" autocomplete="off">
|
||||||
''')
|
"""
|
||||||
|
)
|
||||||
self.assertIn(needle, html)
|
self.assertIn(needle, html)
|
||||||
|
|
||||||
def test_should_list_unarchived_and_user_owned_bookmarks(self):
|
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)
|
visible_bookmarks = self.setup_numbered_bookmarks(3)
|
||||||
invisible_bookmarks = [
|
invisible_bookmarks = [
|
||||||
self.setup_bookmark(is_archived=True),
|
self.setup_bookmark(is_archived=True),
|
||||||
self.setup_bookmark(user=other_user),
|
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.assertVisibleBookmarks(response, visible_bookmarks)
|
||||||
self.assertInvisibleBookmarks(response, invisible_bookmarks)
|
self.assertInvisibleBookmarks(response, invisible_bookmarks)
|
||||||
|
|
||||||
def test_should_list_bookmarks_matching_query(self):
|
def test_should_list_bookmarks_matching_query(self):
|
||||||
visible_bookmarks = self.setup_numbered_bookmarks(3, prefix='foo')
|
visible_bookmarks = self.setup_numbered_bookmarks(3, prefix="foo")
|
||||||
invisible_bookmarks = self.setup_numbered_bookmarks(3, prefix='bar')
|
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.assertVisibleBookmarks(response, visible_bookmarks)
|
||||||
self.assertInvisibleBookmarks(response, invisible_bookmarks)
|
self.assertInvisibleBookmarks(response, invisible_bookmarks)
|
||||||
|
|
||||||
def test_should_list_tags_for_unarchived_and_user_owned_bookmarks(self):
|
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)
|
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')
|
archived_bookmarks = self.setup_numbered_bookmarks(
|
||||||
other_user_bookmarks = self.setup_numbered_bookmarks(3, with_tags=True, user=other_user, tag_prefix='otheruser')
|
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)
|
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.assertVisibleTags(response, visible_tags)
|
||||||
self.assertInvisibleTags(response, invisible_tags)
|
self.assertInvisibleTags(response, invisible_tags)
|
||||||
|
|
||||||
def test_should_list_tags_for_bookmarks_matching_query(self):
|
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')
|
visible_bookmarks = self.setup_numbered_bookmarks(
|
||||||
invisible_bookmarks = self.setup_numbered_bookmarks(3, with_tags=True, prefix='bar', tag_prefix='bar')
|
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)
|
visible_tags = self.get_tags_from_bookmarks(visible_bookmarks)
|
||||||
invisible_tags = self.get_tags_from_bookmarks(invisible_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.assertVisibleTags(response, visible_tags)
|
||||||
self.assertInvisibleTags(response, invisible_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):
|
def test_should_list_bookmarks_and_tags_for_search_preferences(self):
|
||||||
user_profile = self.user.profile
|
user_profile = self.user.profile
|
||||||
user_profile.search_preferences = {
|
user_profile.search_preferences = {
|
||||||
'unread': BookmarkSearch.FILTER_UNREAD_YES,
|
"unread": BookmarkSearch.FILTER_UNREAD_YES,
|
||||||
}
|
}
|
||||||
user_profile.save()
|
user_profile.save()
|
||||||
|
|
||||||
unread_bookmarks = self.setup_numbered_bookmarks(3, unread=True, with_tags=True, prefix='unread',
|
unread_bookmarks = self.setup_numbered_bookmarks(
|
||||||
tag_prefix='unread')
|
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')
|
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)
|
unread_tags = self.get_tags_from_bookmarks(unread_bookmarks)
|
||||||
read_tags = self.get_tags_from_bookmarks(read_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.assertVisibleBookmarks(response, unread_bookmarks)
|
||||||
self.assertInvisibleBookmarks(response, read_bookmarks)
|
self.assertInvisibleBookmarks(response, read_bookmarks)
|
||||||
self.assertVisibleTags(response, unread_tags)
|
self.assertVisibleTags(response, unread_tags)
|
||||||
|
@ -163,11 +199,16 @@ class BookmarkIndexViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
|
||||||
]
|
]
|
||||||
self.setup_bookmark(tags=tags)
|
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]])
|
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 = [
|
tags = [
|
||||||
self.setup_tag(),
|
self.setup_tag(),
|
||||||
self.setup_tag(),
|
self.setup_tag(),
|
||||||
|
@ -177,7 +218,9 @@ class BookmarkIndexViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
|
||||||
]
|
]
|
||||||
self.setup_bookmark(title=tags[0].name, tags=tags)
|
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]])
|
self.assertSelectedTags(response, [tags[1]])
|
||||||
|
|
||||||
|
@ -194,16 +237,18 @@ class BookmarkIndexViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
|
||||||
]
|
]
|
||||||
self.setup_bookmark(tags=tags)
|
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]])
|
self.assertSelectedTags(response, [tags[0], tags[1]])
|
||||||
|
|
||||||
def test_should_open_bookmarks_in_new_page_by_default(self):
|
def test_should_open_bookmarks_in_new_page_by_default(self):
|
||||||
visible_bookmarks = self.setup_numbered_bookmarks(3)
|
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):
|
def test_should_open_bookmarks_in_same_page_if_specified_in_user_profile(self):
|
||||||
user = self.get_or_create_test_user()
|
user = self.get_or_create_test_user()
|
||||||
|
@ -212,71 +257,72 @@ class BookmarkIndexViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
|
||||||
|
|
||||||
visible_bookmarks = self.setup_numbered_bookmarks(3)
|
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):
|
def test_edit_link_return_url_respects_search_options(self):
|
||||||
bookmark = self.setup_bookmark(title='foo')
|
bookmark = self.setup_bookmark(title="foo")
|
||||||
edit_url = reverse('bookmarks:edit', args=[bookmark.id])
|
edit_url = reverse("bookmarks:edit", args=[bookmark.id])
|
||||||
base_url = reverse('bookmarks:index')
|
base_url = reverse("bookmarks:index")
|
||||||
|
|
||||||
# without query params
|
# without query params
|
||||||
return_url = urllib.parse.quote(base_url)
|
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)
|
response = self.client.get(base_url)
|
||||||
self.assertEditLink(response, url)
|
self.assertEditLink(response, url)
|
||||||
|
|
||||||
# with query
|
# with query
|
||||||
url_params = '?q=foo'
|
url_params = "?q=foo"
|
||||||
return_url = urllib.parse.quote(base_url + url_params)
|
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)
|
response = self.client.get(base_url + url_params)
|
||||||
self.assertEditLink(response, url)
|
self.assertEditLink(response, url)
|
||||||
|
|
||||||
# with query and sort and page
|
# 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)
|
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)
|
response = self.client.get(base_url + url_params)
|
||||||
self.assertEditLink(response, url)
|
self.assertEditLink(response, url)
|
||||||
|
|
||||||
def test_bulk_edit_respects_search_options(self):
|
def test_bulk_edit_respects_search_options(self):
|
||||||
action_url = reverse('bookmarks:index.action')
|
action_url = reverse("bookmarks:index.action")
|
||||||
base_url = reverse('bookmarks:index')
|
base_url = reverse("bookmarks:index")
|
||||||
|
|
||||||
# without params
|
# without params
|
||||||
return_url = urllib.parse.quote_plus(base_url)
|
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)
|
response = self.client.get(base_url)
|
||||||
self.assertBulkActionForm(response, url)
|
self.assertBulkActionForm(response, url)
|
||||||
|
|
||||||
# with query
|
# with query
|
||||||
url_params = '?q=foo'
|
url_params = "?q=foo"
|
||||||
return_url = urllib.parse.quote_plus(base_url + url_params)
|
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)
|
response = self.client.get(base_url + url_params)
|
||||||
self.assertBulkActionForm(response, url)
|
self.assertBulkActionForm(response, url)
|
||||||
|
|
||||||
# with query and sort
|
# 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)
|
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)
|
response = self.client.get(base_url + url_params)
|
||||||
self.assertBulkActionForm(response, url)
|
self.assertBulkActionForm(response, url)
|
||||||
|
|
||||||
def test_allowed_bulk_actions(self):
|
def test_allowed_bulk_actions(self):
|
||||||
url = reverse('bookmarks:index')
|
url = reverse("bookmarks:index")
|
||||||
response = self.client.get(url)
|
response = self.client.get(url)
|
||||||
html = response.content.decode()
|
html = response.content.decode()
|
||||||
|
|
||||||
self.assertInHTML(f'''
|
self.assertInHTML(
|
||||||
|
f"""
|
||||||
<select name="bulk_action" class="form-select select-sm">
|
<select name="bulk_action" class="form-select select-sm">
|
||||||
<option value="bulk_archive">Archive</option>
|
<option value="bulk_archive">Archive</option>
|
||||||
<option value="bulk_delete">Delete</option>
|
<option value="bulk_delete">Delete</option>
|
||||||
|
@ -285,18 +331,21 @@ class BookmarkIndexViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
|
||||||
<option value="bulk_read">Mark as read</option>
|
<option value="bulk_read">Mark as read</option>
|
||||||
<option value="bulk_unread">Mark as unread</option>
|
<option value="bulk_unread">Mark as unread</option>
|
||||||
</select>
|
</select>
|
||||||
''', html)
|
""",
|
||||||
|
html,
|
||||||
|
)
|
||||||
|
|
||||||
def test_allowed_bulk_actions_with_sharing_enabled(self):
|
def test_allowed_bulk_actions_with_sharing_enabled(self):
|
||||||
user_profile = self.user.profile
|
user_profile = self.user.profile
|
||||||
user_profile.enable_sharing = True
|
user_profile.enable_sharing = True
|
||||||
user_profile.save()
|
user_profile.save()
|
||||||
|
|
||||||
url = reverse('bookmarks:index')
|
url = reverse("bookmarks:index")
|
||||||
response = self.client.get(url)
|
response = self.client.get(url)
|
||||||
html = response.content.decode()
|
html = response.content.decode()
|
||||||
|
|
||||||
self.assertInHTML(f'''
|
self.assertInHTML(
|
||||||
|
f"""
|
||||||
<select name="bulk_action" class="form-select select-sm">
|
<select name="bulk_action" class="form-select select-sm">
|
||||||
<option value="bulk_archive">Archive</option>
|
<option value="bulk_archive">Archive</option>
|
||||||
<option value="bulk_delete">Delete</option>
|
<option value="bulk_delete">Delete</option>
|
||||||
|
@ -307,142 +356,189 @@ class BookmarkIndexViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
|
||||||
<option value="bulk_share">Share</option>
|
<option value="bulk_share">Share</option>
|
||||||
<option value="bulk_unshare">Unshare</option>
|
<option value="bulk_unshare">Unshare</option>
|
||||||
</select>
|
</select>
|
||||||
''', html)
|
""",
|
||||||
|
html,
|
||||||
|
)
|
||||||
|
|
||||||
def test_apply_search_preferences(self):
|
def test_apply_search_preferences(self):
|
||||||
# no params
|
# 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.status_code, 302)
|
||||||
self.assertEqual(response.url, reverse('bookmarks:index'))
|
self.assertEqual(response.url, reverse("bookmarks:index"))
|
||||||
|
|
||||||
# some params
|
# some params
|
||||||
response = self.client.post(reverse('bookmarks:index'), {
|
response = self.client.post(
|
||||||
'q': 'foo',
|
reverse("bookmarks:index"),
|
||||||
'sort': BookmarkSearch.SORT_TITLE_ASC,
|
{
|
||||||
})
|
"q": "foo",
|
||||||
|
"sort": BookmarkSearch.SORT_TITLE_ASC,
|
||||||
|
},
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 302)
|
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
|
# params with default value are removed
|
||||||
response = self.client.post(reverse('bookmarks:index'), {
|
response = self.client.post(
|
||||||
'q': 'foo',
|
reverse("bookmarks:index"),
|
||||||
'user': '',
|
{
|
||||||
'sort': BookmarkSearch.SORT_ADDED_DESC,
|
"q": "foo",
|
||||||
'shared': BookmarkSearch.FILTER_SHARED_OFF,
|
"user": "",
|
||||||
'unread': BookmarkSearch.FILTER_UNREAD_YES,
|
"sort": BookmarkSearch.SORT_ADDED_DESC,
|
||||||
})
|
"shared": BookmarkSearch.FILTER_SHARED_OFF,
|
||||||
|
"unread": BookmarkSearch.FILTER_UNREAD_YES,
|
||||||
|
},
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 302)
|
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
|
# page is removed
|
||||||
response = self.client.post(reverse('bookmarks:index'), {
|
response = self.client.post(
|
||||||
'q': 'foo',
|
reverse("bookmarks:index"),
|
||||||
'page': '2',
|
{
|
||||||
'sort': BookmarkSearch.SORT_TITLE_ASC,
|
"q": "foo",
|
||||||
})
|
"page": "2",
|
||||||
|
"sort": BookmarkSearch.SORT_TITLE_ASC,
|
||||||
|
},
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 302)
|
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):
|
def test_save_search_preferences(self):
|
||||||
user_profile = self.user.profile
|
user_profile = self.user.profile
|
||||||
|
|
||||||
# no params
|
# no params
|
||||||
self.client.post(reverse('bookmarks:index'), {
|
self.client.post(
|
||||||
'save': '',
|
reverse("bookmarks:index"),
|
||||||
})
|
{
|
||||||
|
"save": "",
|
||||||
|
},
|
||||||
|
)
|
||||||
user_profile.refresh_from_db()
|
user_profile.refresh_from_db()
|
||||||
self.assertEqual(user_profile.search_preferences, {
|
self.assertEqual(
|
||||||
'sort': BookmarkSearch.SORT_ADDED_DESC,
|
user_profile.search_preferences,
|
||||||
'shared': BookmarkSearch.FILTER_SHARED_OFF,
|
{
|
||||||
'unread': BookmarkSearch.FILTER_UNREAD_OFF,
|
"sort": BookmarkSearch.SORT_ADDED_DESC,
|
||||||
})
|
"shared": BookmarkSearch.FILTER_SHARED_OFF,
|
||||||
|
"unread": BookmarkSearch.FILTER_UNREAD_OFF,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
# with param
|
# with param
|
||||||
self.client.post(reverse('bookmarks:index'), {
|
self.client.post(
|
||||||
'save': '',
|
reverse("bookmarks:index"),
|
||||||
'sort': BookmarkSearch.SORT_TITLE_ASC,
|
{
|
||||||
})
|
"save": "",
|
||||||
|
"sort": BookmarkSearch.SORT_TITLE_ASC,
|
||||||
|
},
|
||||||
|
)
|
||||||
user_profile.refresh_from_db()
|
user_profile.refresh_from_db()
|
||||||
self.assertEqual(user_profile.search_preferences, {
|
self.assertEqual(
|
||||||
'sort': BookmarkSearch.SORT_TITLE_ASC,
|
user_profile.search_preferences,
|
||||||
'shared': BookmarkSearch.FILTER_SHARED_OFF,
|
{
|
||||||
'unread': BookmarkSearch.FILTER_UNREAD_OFF,
|
"sort": BookmarkSearch.SORT_TITLE_ASC,
|
||||||
})
|
"shared": BookmarkSearch.FILTER_SHARED_OFF,
|
||||||
|
"unread": BookmarkSearch.FILTER_UNREAD_OFF,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
# add a param
|
# add a param
|
||||||
self.client.post(reverse('bookmarks:index'), {
|
self.client.post(
|
||||||
'save': '',
|
reverse("bookmarks:index"),
|
||||||
'sort': BookmarkSearch.SORT_TITLE_ASC,
|
{
|
||||||
'unread': BookmarkSearch.FILTER_UNREAD_YES,
|
"save": "",
|
||||||
})
|
"sort": BookmarkSearch.SORT_TITLE_ASC,
|
||||||
|
"unread": BookmarkSearch.FILTER_UNREAD_YES,
|
||||||
|
},
|
||||||
|
)
|
||||||
user_profile.refresh_from_db()
|
user_profile.refresh_from_db()
|
||||||
self.assertEqual(user_profile.search_preferences, {
|
self.assertEqual(
|
||||||
'sort': BookmarkSearch.SORT_TITLE_ASC,
|
user_profile.search_preferences,
|
||||||
'shared': BookmarkSearch.FILTER_SHARED_OFF,
|
{
|
||||||
'unread': BookmarkSearch.FILTER_UNREAD_YES,
|
"sort": BookmarkSearch.SORT_TITLE_ASC,
|
||||||
})
|
"shared": BookmarkSearch.FILTER_SHARED_OFF,
|
||||||
|
"unread": BookmarkSearch.FILTER_UNREAD_YES,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
# remove a param
|
# remove a param
|
||||||
self.client.post(reverse('bookmarks:index'), {
|
self.client.post(
|
||||||
'save': '',
|
reverse("bookmarks:index"),
|
||||||
'unread': BookmarkSearch.FILTER_UNREAD_YES,
|
{
|
||||||
})
|
"save": "",
|
||||||
|
"unread": BookmarkSearch.FILTER_UNREAD_YES,
|
||||||
|
},
|
||||||
|
)
|
||||||
user_profile.refresh_from_db()
|
user_profile.refresh_from_db()
|
||||||
self.assertEqual(user_profile.search_preferences, {
|
self.assertEqual(
|
||||||
'sort': BookmarkSearch.SORT_ADDED_DESC,
|
user_profile.search_preferences,
|
||||||
'shared': BookmarkSearch.FILTER_SHARED_OFF,
|
{
|
||||||
'unread': BookmarkSearch.FILTER_UNREAD_YES,
|
"sort": BookmarkSearch.SORT_ADDED_DESC,
|
||||||
})
|
"shared": BookmarkSearch.FILTER_SHARED_OFF,
|
||||||
|
"unread": BookmarkSearch.FILTER_UNREAD_YES,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
# ignores non-preferences
|
# ignores non-preferences
|
||||||
self.client.post(reverse('bookmarks:index'), {
|
self.client.post(
|
||||||
'save': '',
|
reverse("bookmarks:index"),
|
||||||
'q': 'foo',
|
{
|
||||||
'user': 'john',
|
"save": "",
|
||||||
'page': '3',
|
"q": "foo",
|
||||||
'sort': BookmarkSearch.SORT_TITLE_ASC,
|
"user": "john",
|
||||||
})
|
"page": "3",
|
||||||
|
"sort": BookmarkSearch.SORT_TITLE_ASC,
|
||||||
|
},
|
||||||
|
)
|
||||||
user_profile.refresh_from_db()
|
user_profile.refresh_from_db()
|
||||||
self.assertEqual(user_profile.search_preferences, {
|
self.assertEqual(
|
||||||
'sort': BookmarkSearch.SORT_TITLE_ASC,
|
user_profile.search_preferences,
|
||||||
'shared': BookmarkSearch.FILTER_SHARED_OFF,
|
{
|
||||||
'unread': BookmarkSearch.FILTER_UNREAD_OFF,
|
"sort": BookmarkSearch.SORT_TITLE_ASC,
|
||||||
})
|
"shared": BookmarkSearch.FILTER_SHARED_OFF,
|
||||||
|
"unread": BookmarkSearch.FILTER_UNREAD_OFF,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
def test_url_encode_bookmark_actions_url(self):
|
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)
|
response = self.client.get(url)
|
||||||
html = response.content.decode()
|
html = response.content.decode()
|
||||||
soup = self.make_soup(html)
|
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'],
|
self.assertEqual(
|
||||||
'/bookmarks/action?q=%23foo&return_url=%2Fbookmarks%3Fq%3D%2523foo')
|
actions_form.attrs["action"],
|
||||||
|
"/bookmarks/action?q=%23foo&return_url=%2Fbookmarks%3Fq%3D%2523foo",
|
||||||
|
)
|
||||||
|
|
||||||
def test_encode_search_params(self):
|
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)
|
response = self.client.get(url)
|
||||||
self.assertNotContains(response, 'alert(\'xss\')')
|
self.assertNotContains(response, "alert('xss')")
|
||||||
self.assertContains(response, bookmark.url)
|
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)
|
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)
|
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)
|
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)
|
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)
|
response = self.client.get(url)
|
||||||
self.assertNotContains(response, 'alert(\'xss\')')
|
self.assertNotContains(response, "alert('xss')")
|
||||||
|
|
|
@ -26,8 +26,10 @@ class BookmarkIndexViewPerformanceTestCase(TransactionTestCase, BookmarkFactoryM
|
||||||
# capture number of queries
|
# capture number of queries
|
||||||
context = CaptureQueriesContext(self.get_connection())
|
context = CaptureQueriesContext(self.get_connection())
|
||||||
with context:
|
with context:
|
||||||
response = self.client.get(reverse('bookmarks:index'))
|
response = self.client.get(reverse("bookmarks:index"))
|
||||||
self.assertContains(response, '<li ld-bookmark-item>', num_initial_bookmarks)
|
self.assertContains(
|
||||||
|
response, "<li ld-bookmark-item>", num_initial_bookmarks
|
||||||
|
)
|
||||||
|
|
||||||
number_of_queries = context.final_queries
|
number_of_queries = context.final_queries
|
||||||
|
|
||||||
|
@ -38,5 +40,9 @@ class BookmarkIndexViewPerformanceTestCase(TransactionTestCase, BookmarkFactoryM
|
||||||
|
|
||||||
# assert num queries doesn't increase
|
# assert num queries doesn't increase
|
||||||
with self.assertNumQueries(number_of_queries):
|
with self.assertNumQueries(number_of_queries):
|
||||||
response = self.client.get(reverse('bookmarks:index'))
|
response = self.client.get(reverse("bookmarks:index"))
|
||||||
self.assertContains(response, '<li ld-bookmark-item>', num_initial_bookmarks + num_additional_bookmarks)
|
self.assertContains(
|
||||||
|
response,
|
||||||
|
"<li ld-bookmark-item>",
|
||||||
|
num_initial_bookmarks + num_additional_bookmarks,
|
||||||
|
)
|
||||||
|
|
|
@ -15,41 +15,41 @@ class BookmarkNewViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
if overrides is None:
|
if overrides is None:
|
||||||
overrides = {}
|
overrides = {}
|
||||||
form_data = {
|
form_data = {
|
||||||
'url': 'http://example.com',
|
"url": "http://example.com",
|
||||||
'tag_string': 'tag1 tag2',
|
"tag_string": "tag1 tag2",
|
||||||
'title': 'test title',
|
"title": "test title",
|
||||||
'description': 'test description',
|
"description": "test description",
|
||||||
'notes': 'test notes',
|
"notes": "test notes",
|
||||||
'unread': False,
|
"unread": False,
|
||||||
'shared': False,
|
"shared": False,
|
||||||
'auto_close': '',
|
"auto_close": "",
|
||||||
}
|
}
|
||||||
return {**form_data, **overrides}
|
return {**form_data, **overrides}
|
||||||
|
|
||||||
def test_should_create_new_bookmark(self):
|
def test_should_create_new_bookmark(self):
|
||||||
form_data = self.create_form_data()
|
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)
|
self.assertEqual(Bookmark.objects.count(), 1)
|
||||||
|
|
||||||
bookmark = Bookmark.objects.first()
|
bookmark = Bookmark.objects.first()
|
||||||
self.assertEqual(bookmark.owner, self.user)
|
self.assertEqual(bookmark.owner, self.user)
|
||||||
self.assertEqual(bookmark.url, form_data['url'])
|
self.assertEqual(bookmark.url, form_data["url"])
|
||||||
self.assertEqual(bookmark.title, form_data['title'])
|
self.assertEqual(bookmark.title, form_data["title"])
|
||||||
self.assertEqual(bookmark.description, form_data['description'])
|
self.assertEqual(bookmark.description, form_data["description"])
|
||||||
self.assertEqual(bookmark.notes, form_data['notes'])
|
self.assertEqual(bookmark.notes, form_data["notes"])
|
||||||
self.assertEqual(bookmark.unread, form_data['unread'])
|
self.assertEqual(bookmark.unread, form_data["unread"])
|
||||||
self.assertEqual(bookmark.shared, form_data['shared'])
|
self.assertEqual(bookmark.shared, form_data["shared"])
|
||||||
self.assertEqual(bookmark.tags.count(), 2)
|
self.assertEqual(bookmark.tags.count(), 2)
|
||||||
tags = bookmark.tags.order_by('name').all()
|
tags = bookmark.tags.order_by("name").all()
|
||||||
self.assertEqual(tags[0].name, 'tag1')
|
self.assertEqual(tags[0].name, "tag1")
|
||||||
self.assertEqual(tags[1].name, 'tag2')
|
self.assertEqual(tags[1].name, "tag2")
|
||||||
|
|
||||||
def test_should_create_new_unread_bookmark(self):
|
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)
|
self.assertEqual(Bookmark.objects.count(), 1)
|
||||||
|
|
||||||
|
@ -57,9 +57,9 @@ class BookmarkNewViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
self.assertTrue(bookmark.unread)
|
self.assertTrue(bookmark.unread)
|
||||||
|
|
||||||
def test_should_create_new_shared_bookmark(self):
|
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)
|
self.assertEqual(Bookmark.objects.count(), 1)
|
||||||
|
|
||||||
|
@ -67,124 +67,146 @@ class BookmarkNewViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
self.assertTrue(bookmark.shared)
|
self.assertTrue(bookmark.shared)
|
||||||
|
|
||||||
def test_should_prefill_url_from_url_parameter(self):
|
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()
|
html = response.content.decode()
|
||||||
|
|
||||||
self.assertInHTML(
|
self.assertInHTML(
|
||||||
'<input type="text" name="url" value="http://example.com" '
|
'<input type="text" name="url" value="http://example.com" '
|
||||||
'placeholder=" " autofocus class="form-input" required '
|
'placeholder=" " autofocus class="form-input" required '
|
||||||
'id="id_url">',
|
'id="id_url">',
|
||||||
html)
|
html,
|
||||||
|
)
|
||||||
|
|
||||||
def test_should_prefill_title_from_url_parameter(self):
|
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()
|
html = response.content.decode()
|
||||||
|
|
||||||
self.assertInHTML(
|
self.assertInHTML(
|
||||||
'<input type="text" name="title" value="Example Title" '
|
'<input type="text" name="title" value="Example Title" '
|
||||||
'class="form-input" maxlength="512" autocomplete="off" '
|
'class="form-input" maxlength="512" autocomplete="off" '
|
||||||
'id="id_title">',
|
'id="id_title">',
|
||||||
html)
|
html,
|
||||||
|
)
|
||||||
|
|
||||||
def test_should_prefill_description_from_url_parameter(self):
|
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()
|
html = response.content.decode()
|
||||||
|
|
||||||
self.assertInHTML(
|
self.assertInHTML(
|
||||||
'<textarea name="description" class="form-input" cols="40" '
|
'<textarea name="description" class="form-input" cols="40" '
|
||||||
'rows="2" id="id_description">Example Site Description</textarea>',
|
'rows="2" id="id_description">Example Site Description</textarea>',
|
||||||
html)
|
html,
|
||||||
|
)
|
||||||
|
|
||||||
def test_should_enable_auto_close_when_specified_in_url_parameter(self):
|
def test_should_enable_auto_close_when_specified_in_url_parameter(self):
|
||||||
response = self.client.get(
|
response = self.client.get(reverse("bookmarks:new") + "?auto_close")
|
||||||
reverse('bookmarks:new') + '?auto_close')
|
|
||||||
html = response.content.decode()
|
html = response.content.decode()
|
||||||
|
|
||||||
self.assertInHTML(
|
self.assertInHTML(
|
||||||
'<input type="hidden" name="auto_close" value="true" '
|
'<input type="hidden" name="auto_close" value="true" '
|
||||||
'id="id_auto_close">',
|
'id="id_auto_close">',
|
||||||
html)
|
html,
|
||||||
|
)
|
||||||
|
|
||||||
def test_should_not_enable_auto_close_when_not_specified_in_url_parameter(
|
def test_should_not_enable_auto_close_when_not_specified_in_url_parameter(self):
|
||||||
self):
|
response = self.client.get(reverse("bookmarks:new"))
|
||||||
response = self.client.get(reverse('bookmarks:new'))
|
|
||||||
html = response.content.decode()
|
html = response.content.decode()
|
||||||
|
|
||||||
self.assertInHTML('<input type="hidden" name="auto_close" id="id_auto_close">', html)
|
self.assertInHTML(
|
||||||
|
'<input type="hidden" name="auto_close" id="id_auto_close">', html
|
||||||
|
)
|
||||||
|
|
||||||
def test_should_redirect_to_index_view(self):
|
def test_should_redirect_to_index_view(self):
|
||||||
form_data = self.create_form_data()
|
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):
|
def test_should_not_redirect_to_external_url(self):
|
||||||
form_data = self.create_form_data()
|
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):
|
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):
|
def test_should_respect_share_profile_setting(self):
|
||||||
self.user.profile.enable_sharing = False
|
self.user.profile.enable_sharing = False
|
||||||
self.user.profile.save()
|
self.user.profile.save()
|
||||||
response = self.client.get(reverse('bookmarks:new'))
|
response = self.client.get(reverse("bookmarks:new"))
|
||||||
html = response.content.decode()
|
html = response.content.decode()
|
||||||
|
|
||||||
self.assertInHTML('''
|
self.assertInHTML(
|
||||||
|
"""
|
||||||
<label for="id_shared" class="form-checkbox">
|
<label for="id_shared" class="form-checkbox">
|
||||||
<input type="checkbox" name="shared" id="id_shared">
|
<input type="checkbox" name="shared" id="id_shared">
|
||||||
<i class="form-icon"></i>
|
<i class="form-icon"></i>
|
||||||
<span>Share</span>
|
<span>Share</span>
|
||||||
</label>
|
</label>
|
||||||
''', html, count=0)
|
""",
|
||||||
|
html,
|
||||||
|
count=0,
|
||||||
|
)
|
||||||
|
|
||||||
self.user.profile.enable_sharing = True
|
self.user.profile.enable_sharing = True
|
||||||
self.user.profile.save()
|
self.user.profile.save()
|
||||||
response = self.client.get(reverse('bookmarks:new'))
|
response = self.client.get(reverse("bookmarks:new"))
|
||||||
html = response.content.decode()
|
html = response.content.decode()
|
||||||
|
|
||||||
self.assertInHTML('''
|
self.assertInHTML(
|
||||||
|
"""
|
||||||
<label for="id_shared" class="form-checkbox">
|
<label for="id_shared" class="form-checkbox">
|
||||||
<input type="checkbox" name="shared" id="id_shared">
|
<input type="checkbox" name="shared" id="id_shared">
|
||||||
<i class="form-icon"></i>
|
<i class="form-icon"></i>
|
||||||
<span>Share</span>
|
<span>Share</span>
|
||||||
</label>
|
</label>
|
||||||
''', html, count=1)
|
""",
|
||||||
|
html,
|
||||||
|
count=1,
|
||||||
|
)
|
||||||
|
|
||||||
def test_should_show_respective_share_hint(self):
|
def test_should_show_respective_share_hint(self):
|
||||||
self.user.profile.enable_sharing = True
|
self.user.profile.enable_sharing = True
|
||||||
self.user.profile.save()
|
self.user.profile.save()
|
||||||
|
|
||||||
response = self.client.get(reverse('bookmarks:new'))
|
response = self.client.get(reverse("bookmarks:new"))
|
||||||
html = response.content.decode()
|
html = response.content.decode()
|
||||||
self.assertInHTML('''
|
self.assertInHTML(
|
||||||
|
"""
|
||||||
<div class="form-input-hint">
|
<div class="form-input-hint">
|
||||||
Share this bookmark with other registered users.
|
Share this bookmark with other registered users.
|
||||||
</div>
|
</div>
|
||||||
''', html)
|
""",
|
||||||
|
html,
|
||||||
|
)
|
||||||
|
|
||||||
self.user.profile.enable_public_sharing = True
|
self.user.profile.enable_public_sharing = True
|
||||||
self.user.profile.save()
|
self.user.profile.save()
|
||||||
|
|
||||||
response = self.client.get(reverse('bookmarks:new'))
|
response = self.client.get(reverse("bookmarks:new"))
|
||||||
html = response.content.decode()
|
html = response.content.decode()
|
||||||
self.assertInHTML('''
|
self.assertInHTML(
|
||||||
|
"""
|
||||||
<div class="form-input-hint">
|
<div class="form-input-hint">
|
||||||
Share this bookmark with other registered users and anonymous users.
|
Share this bookmark with other registered users and anonymous users.
|
||||||
</div>
|
</div>
|
||||||
''', html)
|
""",
|
||||||
|
html,
|
||||||
|
)
|
||||||
|
|
||||||
def test_should_hide_notes_if_there_are_no_notes(self):
|
def test_should_hide_notes_if_there_are_no_notes(self):
|
||||||
bookmark = self.setup_bookmark()
|
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, '<details class="notes">', count=1)
|
self.assertContains(response, '<details class="notes">', count=1)
|
||||||
|
|
|
@ -9,40 +9,45 @@ class BookmarkSearchFormTest(TestCase, BookmarkFactoryMixin):
|
||||||
# no params
|
# no params
|
||||||
search = BookmarkSearch()
|
search = BookmarkSearch()
|
||||||
form = BookmarkSearchForm(search)
|
form = BookmarkSearchForm(search)
|
||||||
self.assertEqual(form['q'].initial, '')
|
self.assertEqual(form["q"].initial, "")
|
||||||
self.assertEqual(form['user'].initial, '')
|
self.assertEqual(form["user"].initial, "")
|
||||||
self.assertEqual(form['sort'].initial, BookmarkSearch.SORT_ADDED_DESC)
|
self.assertEqual(form["sort"].initial, BookmarkSearch.SORT_ADDED_DESC)
|
||||||
self.assertEqual(form['shared'].initial, BookmarkSearch.FILTER_SHARED_OFF)
|
self.assertEqual(form["shared"].initial, BookmarkSearch.FILTER_SHARED_OFF)
|
||||||
self.assertEqual(form['unread'].initial, BookmarkSearch.FILTER_UNREAD_OFF)
|
self.assertEqual(form["unread"].initial, BookmarkSearch.FILTER_UNREAD_OFF)
|
||||||
|
|
||||||
# with params
|
# with params
|
||||||
search = BookmarkSearch(q='search query',
|
search = BookmarkSearch(
|
||||||
|
q="search query",
|
||||||
sort=BookmarkSearch.SORT_ADDED_ASC,
|
sort=BookmarkSearch.SORT_ADDED_ASC,
|
||||||
user='user123',
|
user="user123",
|
||||||
shared=BookmarkSearch.FILTER_SHARED_SHARED,
|
shared=BookmarkSearch.FILTER_SHARED_SHARED,
|
||||||
unread=BookmarkSearch.FILTER_UNREAD_YES)
|
unread=BookmarkSearch.FILTER_UNREAD_YES,
|
||||||
|
)
|
||||||
form = BookmarkSearchForm(search)
|
form = BookmarkSearchForm(search)
|
||||||
self.assertEqual(form['q'].initial, 'search query')
|
self.assertEqual(form["q"].initial, "search query")
|
||||||
self.assertEqual(form['user'].initial, 'user123')
|
self.assertEqual(form["user"].initial, "user123")
|
||||||
self.assertEqual(form['sort'].initial, BookmarkSearch.SORT_ADDED_ASC)
|
self.assertEqual(form["sort"].initial, BookmarkSearch.SORT_ADDED_ASC)
|
||||||
self.assertEqual(form['shared'].initial, BookmarkSearch.FILTER_SHARED_SHARED)
|
self.assertEqual(form["shared"].initial, BookmarkSearch.FILTER_SHARED_SHARED)
|
||||||
self.assertEqual(form['unread'].initial, BookmarkSearch.FILTER_UNREAD_YES)
|
self.assertEqual(form["unread"].initial, BookmarkSearch.FILTER_UNREAD_YES)
|
||||||
|
|
||||||
def test_user_options(self):
|
def test_user_options(self):
|
||||||
users = [
|
users = [
|
||||||
self.setup_user('user1'),
|
self.setup_user("user1"),
|
||||||
self.setup_user('user2'),
|
self.setup_user("user2"),
|
||||||
self.setup_user('user3'),
|
self.setup_user("user3"),
|
||||||
]
|
]
|
||||||
search = BookmarkSearch()
|
search = BookmarkSearch()
|
||||||
form = BookmarkSearchForm(search, users=users)
|
form = BookmarkSearchForm(search, users=users)
|
||||||
|
|
||||||
self.assertCountEqual(form['user'].field.choices, [
|
self.assertCountEqual(
|
||||||
('', 'Everyone'),
|
form["user"].field.choices,
|
||||||
('user1', 'user1'),
|
[
|
||||||
('user2', 'user2'),
|
("", "Everyone"),
|
||||||
('user3', 'user3'),
|
("user1", "user1"),
|
||||||
])
|
("user2", "user2"),
|
||||||
|
("user3", "user3"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
def test_hidden_fields(self):
|
def test_hidden_fields(self):
|
||||||
# no modified params
|
# no modified params
|
||||||
|
@ -51,24 +56,27 @@ class BookmarkSearchFormTest(TestCase, BookmarkFactoryMixin):
|
||||||
self.assertEqual(len(form.hidden_fields()), 0)
|
self.assertEqual(len(form.hidden_fields()), 0)
|
||||||
|
|
||||||
# some modified params
|
# some modified params
|
||||||
search = BookmarkSearch(q='search query',
|
search = BookmarkSearch(q="search query", sort=BookmarkSearch.SORT_ADDED_ASC)
|
||||||
sort=BookmarkSearch.SORT_ADDED_ASC)
|
|
||||||
form = BookmarkSearchForm(search)
|
form = BookmarkSearchForm(search)
|
||||||
self.assertCountEqual(form.hidden_fields(), [form['q'], form['sort']])
|
self.assertCountEqual(form.hidden_fields(), [form["q"], form["sort"]])
|
||||||
|
|
||||||
# all modified params
|
# all modified params
|
||||||
search = BookmarkSearch(q='search query',
|
search = BookmarkSearch(
|
||||||
|
q="search query",
|
||||||
sort=BookmarkSearch.SORT_ADDED_ASC,
|
sort=BookmarkSearch.SORT_ADDED_ASC,
|
||||||
user='user123',
|
user="user123",
|
||||||
shared=BookmarkSearch.FILTER_SHARED_SHARED,
|
shared=BookmarkSearch.FILTER_SHARED_SHARED,
|
||||||
unread=BookmarkSearch.FILTER_UNREAD_YES)
|
unread=BookmarkSearch.FILTER_UNREAD_YES,
|
||||||
|
)
|
||||||
form = BookmarkSearchForm(search)
|
form = BookmarkSearchForm(search)
|
||||||
self.assertCountEqual(form.hidden_fields(),
|
self.assertCountEqual(
|
||||||
[form['q'], form['sort'], form['user'], form['shared'], form['unread']])
|
form.hidden_fields(),
|
||||||
|
[form["q"], form["sort"], form["user"], form["shared"], form["unread"]],
|
||||||
|
)
|
||||||
|
|
||||||
# some modified params are editable fields
|
# some modified params are editable fields
|
||||||
search = BookmarkSearch(q='search query',
|
search = BookmarkSearch(
|
||||||
sort=BookmarkSearch.SORT_ADDED_ASC,
|
q="search query", sort=BookmarkSearch.SORT_ADDED_ASC, user="user123"
|
||||||
user='user123')
|
)
|
||||||
form = BookmarkSearchForm(search, editable_fields=['q', 'user'])
|
form = BookmarkSearchForm(search, editable_fields=["q", "user"])
|
||||||
self.assertCountEqual(form.hidden_fields(), [form['sort']])
|
self.assertCountEqual(form.hidden_fields(), [form["sort"]])
|
||||||
|
|
|
@ -10,57 +10,59 @@ class BookmarkSearchModelTest(TestCase):
|
||||||
query_dict = QueryDict()
|
query_dict = QueryDict()
|
||||||
|
|
||||||
search = BookmarkSearch.from_request(query_dict)
|
search = BookmarkSearch.from_request(query_dict)
|
||||||
self.assertEqual(search.q, '')
|
self.assertEqual(search.q, "")
|
||||||
self.assertEqual(search.user, '')
|
self.assertEqual(search.user, "")
|
||||||
self.assertEqual(search.sort, BookmarkSearch.SORT_ADDED_DESC)
|
self.assertEqual(search.sort, BookmarkSearch.SORT_ADDED_DESC)
|
||||||
self.assertEqual(search.shared, BookmarkSearch.FILTER_SHARED_OFF)
|
self.assertEqual(search.shared, BookmarkSearch.FILTER_SHARED_OFF)
|
||||||
self.assertEqual(search.unread, BookmarkSearch.FILTER_UNREAD_OFF)
|
self.assertEqual(search.unread, BookmarkSearch.FILTER_UNREAD_OFF)
|
||||||
|
|
||||||
# some params
|
# 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)
|
bookmark_search = BookmarkSearch.from_request(query_dict)
|
||||||
self.assertEqual(bookmark_search.q, 'search query')
|
self.assertEqual(bookmark_search.q, "search query")
|
||||||
self.assertEqual(bookmark_search.user, 'user123')
|
self.assertEqual(bookmark_search.user, "user123")
|
||||||
self.assertEqual(bookmark_search.sort, BookmarkSearch.SORT_ADDED_DESC)
|
self.assertEqual(bookmark_search.sort, BookmarkSearch.SORT_ADDED_DESC)
|
||||||
self.assertEqual(search.shared, BookmarkSearch.FILTER_SHARED_OFF)
|
self.assertEqual(search.shared, BookmarkSearch.FILTER_SHARED_OFF)
|
||||||
self.assertEqual(search.unread, BookmarkSearch.FILTER_UNREAD_OFF)
|
self.assertEqual(search.unread, BookmarkSearch.FILTER_UNREAD_OFF)
|
||||||
|
|
||||||
# all params
|
# 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)
|
search = BookmarkSearch.from_request(query_dict)
|
||||||
self.assertEqual(search.q, 'search query')
|
self.assertEqual(search.q, "search query")
|
||||||
self.assertEqual(search.user, 'user123')
|
self.assertEqual(search.user, "user123")
|
||||||
self.assertEqual(search.sort, BookmarkSearch.SORT_TITLE_ASC)
|
self.assertEqual(search.sort, BookmarkSearch.SORT_TITLE_ASC)
|
||||||
self.assertEqual(search.shared, BookmarkSearch.FILTER_SHARED_SHARED)
|
self.assertEqual(search.shared, BookmarkSearch.FILTER_SHARED_SHARED)
|
||||||
self.assertEqual(search.unread, BookmarkSearch.FILTER_UNREAD_YES)
|
self.assertEqual(search.unread, BookmarkSearch.FILTER_UNREAD_YES)
|
||||||
|
|
||||||
# respects preferences
|
# respects preferences
|
||||||
preferences = {
|
preferences = {
|
||||||
'sort': BookmarkSearch.SORT_TITLE_ASC,
|
"sort": BookmarkSearch.SORT_TITLE_ASC,
|
||||||
'unread': BookmarkSearch.FILTER_UNREAD_YES,
|
"unread": BookmarkSearch.FILTER_UNREAD_YES,
|
||||||
}
|
}
|
||||||
query_dict = QueryDict('q=search query')
|
query_dict = QueryDict("q=search query")
|
||||||
|
|
||||||
search = BookmarkSearch.from_request(query_dict, preferences)
|
search = BookmarkSearch.from_request(query_dict, preferences)
|
||||||
self.assertEqual(search.q, 'search query')
|
self.assertEqual(search.q, "search query")
|
||||||
self.assertEqual(search.user, '')
|
self.assertEqual(search.user, "")
|
||||||
self.assertEqual(search.sort, BookmarkSearch.SORT_TITLE_ASC)
|
self.assertEqual(search.sort, BookmarkSearch.SORT_TITLE_ASC)
|
||||||
self.assertEqual(search.shared, BookmarkSearch.FILTER_SHARED_OFF)
|
self.assertEqual(search.shared, BookmarkSearch.FILTER_SHARED_OFF)
|
||||||
self.assertEqual(search.unread, BookmarkSearch.FILTER_UNREAD_YES)
|
self.assertEqual(search.unread, BookmarkSearch.FILTER_UNREAD_YES)
|
||||||
|
|
||||||
# query overrides preferences
|
# query overrides preferences
|
||||||
preferences = {
|
preferences = {
|
||||||
'sort': BookmarkSearch.SORT_TITLE_ASC,
|
"sort": BookmarkSearch.SORT_TITLE_ASC,
|
||||||
'shared': BookmarkSearch.FILTER_SHARED_SHARED,
|
"shared": BookmarkSearch.FILTER_SHARED_SHARED,
|
||||||
'unread': BookmarkSearch.FILTER_UNREAD_YES,
|
"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)
|
search = BookmarkSearch.from_request(query_dict, preferences)
|
||||||
self.assertEqual(search.q, '')
|
self.assertEqual(search.q, "")
|
||||||
self.assertEqual(search.user, '')
|
self.assertEqual(search.user, "")
|
||||||
self.assertEqual(search.sort, BookmarkSearch.SORT_TITLE_DESC)
|
self.assertEqual(search.sort, BookmarkSearch.SORT_TITLE_DESC)
|
||||||
self.assertEqual(search.shared, BookmarkSearch.FILTER_SHARED_UNSHARED)
|
self.assertEqual(search.shared, BookmarkSearch.FILTER_SHARED_UNSHARED)
|
||||||
self.assertEqual(search.unread, BookmarkSearch.FILTER_UNREAD_OFF)
|
self.assertEqual(search.unread, BookmarkSearch.FILTER_UNREAD_OFF)
|
||||||
|
@ -72,28 +74,36 @@ class BookmarkSearchModelTest(TestCase):
|
||||||
self.assertEqual(len(modified_params), 0)
|
self.assertEqual(len(modified_params), 0)
|
||||||
|
|
||||||
# params are default values
|
# 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
|
modified_params = bookmark_search.modified_params
|
||||||
self.assertEqual(len(modified_params), 0)
|
self.assertEqual(len(modified_params), 0)
|
||||||
|
|
||||||
# some modified params
|
# 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
|
modified_params = bookmark_search.modified_params
|
||||||
self.assertCountEqual(modified_params, ['q', 'sort'])
|
self.assertCountEqual(modified_params, ["q", "sort"])
|
||||||
|
|
||||||
# all modified params
|
# all modified params
|
||||||
bookmark_search = BookmarkSearch(q='search query',
|
bookmark_search = BookmarkSearch(
|
||||||
|
q="search query",
|
||||||
sort=BookmarkSearch.SORT_ADDED_ASC,
|
sort=BookmarkSearch.SORT_ADDED_ASC,
|
||||||
user='user123',
|
user="user123",
|
||||||
shared=BookmarkSearch.FILTER_SHARED_SHARED,
|
shared=BookmarkSearch.FILTER_SHARED_SHARED,
|
||||||
unread=BookmarkSearch.FILTER_UNREAD_YES)
|
unread=BookmarkSearch.FILTER_UNREAD_YES,
|
||||||
|
)
|
||||||
modified_params = bookmark_search.modified_params
|
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 are not modified params
|
||||||
preferences = {
|
preferences = {
|
||||||
'sort': BookmarkSearch.SORT_TITLE_ASC,
|
"sort": BookmarkSearch.SORT_TITLE_ASC,
|
||||||
'unread': BookmarkSearch.FILTER_UNREAD_YES,
|
"unread": BookmarkSearch.FILTER_UNREAD_YES,
|
||||||
}
|
}
|
||||||
bookmark_search = BookmarkSearch(preferences=preferences)
|
bookmark_search = BookmarkSearch(preferences=preferences)
|
||||||
modified_params = bookmark_search.modified_params
|
modified_params = bookmark_search.modified_params
|
||||||
|
@ -101,27 +111,31 @@ class BookmarkSearchModelTest(TestCase):
|
||||||
|
|
||||||
# param is not modified if it matches the preference
|
# param is not modified if it matches the preference
|
||||||
preferences = {
|
preferences = {
|
||||||
'sort': BookmarkSearch.SORT_TITLE_ASC,
|
"sort": BookmarkSearch.SORT_TITLE_ASC,
|
||||||
'unread': BookmarkSearch.FILTER_UNREAD_YES,
|
"unread": BookmarkSearch.FILTER_UNREAD_YES,
|
||||||
}
|
}
|
||||||
bookmark_search = BookmarkSearch(sort=BookmarkSearch.SORT_TITLE_ASC,
|
bookmark_search = BookmarkSearch(
|
||||||
|
sort=BookmarkSearch.SORT_TITLE_ASC,
|
||||||
unread=BookmarkSearch.FILTER_UNREAD_YES,
|
unread=BookmarkSearch.FILTER_UNREAD_YES,
|
||||||
preferences=preferences)
|
preferences=preferences,
|
||||||
|
)
|
||||||
modified_params = bookmark_search.modified_params
|
modified_params = bookmark_search.modified_params
|
||||||
self.assertEqual(len(modified_params), 0)
|
self.assertEqual(len(modified_params), 0)
|
||||||
|
|
||||||
# overriding preferences is a modified param
|
# overriding preferences is a modified param
|
||||||
preferences = {
|
preferences = {
|
||||||
'sort': BookmarkSearch.SORT_TITLE_ASC,
|
"sort": BookmarkSearch.SORT_TITLE_ASC,
|
||||||
'shared': BookmarkSearch.FILTER_SHARED_SHARED,
|
"shared": BookmarkSearch.FILTER_SHARED_SHARED,
|
||||||
'unread': BookmarkSearch.FILTER_UNREAD_YES,
|
"unread": BookmarkSearch.FILTER_UNREAD_YES,
|
||||||
}
|
}
|
||||||
bookmark_search = BookmarkSearch(sort=BookmarkSearch.SORT_TITLE_DESC,
|
bookmark_search = BookmarkSearch(
|
||||||
|
sort=BookmarkSearch.SORT_TITLE_DESC,
|
||||||
shared=BookmarkSearch.FILTER_SHARED_UNSHARED,
|
shared=BookmarkSearch.FILTER_SHARED_UNSHARED,
|
||||||
unread=BookmarkSearch.FILTER_UNREAD_OFF,
|
unread=BookmarkSearch.FILTER_UNREAD_OFF,
|
||||||
preferences=preferences)
|
preferences=preferences,
|
||||||
|
)
|
||||||
modified_params = bookmark_search.modified_params
|
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):
|
def test_has_modifications(self):
|
||||||
# no params
|
# no params
|
||||||
|
@ -129,34 +143,49 @@ class BookmarkSearchModelTest(TestCase):
|
||||||
self.assertFalse(bookmark_search.has_modifications)
|
self.assertFalse(bookmark_search.has_modifications)
|
||||||
|
|
||||||
# params are default values
|
# 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)
|
self.assertFalse(bookmark_search.has_modifications)
|
||||||
|
|
||||||
# modified params
|
# 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)
|
self.assertTrue(bookmark_search.has_modifications)
|
||||||
|
|
||||||
def test_preferences_dict(self):
|
def test_preferences_dict(self):
|
||||||
# no params
|
# no params
|
||||||
bookmark_search = BookmarkSearch()
|
bookmark_search = BookmarkSearch()
|
||||||
self.assertEqual(bookmark_search.preferences_dict, {
|
self.assertEqual(
|
||||||
'sort': BookmarkSearch.SORT_ADDED_DESC,
|
bookmark_search.preferences_dict,
|
||||||
'shared': BookmarkSearch.FILTER_SHARED_OFF,
|
{
|
||||||
'unread': BookmarkSearch.FILTER_UNREAD_OFF,
|
"sort": BookmarkSearch.SORT_ADDED_DESC,
|
||||||
})
|
"shared": BookmarkSearch.FILTER_SHARED_OFF,
|
||||||
|
"unread": BookmarkSearch.FILTER_UNREAD_OFF,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
# with params
|
# with params
|
||||||
bookmark_search = BookmarkSearch(sort=BookmarkSearch.SORT_TITLE_DESC, unread=BookmarkSearch.FILTER_UNREAD_YES)
|
bookmark_search = BookmarkSearch(
|
||||||
self.assertEqual(bookmark_search.preferences_dict, {
|
sort=BookmarkSearch.SORT_TITLE_DESC, unread=BookmarkSearch.FILTER_UNREAD_YES
|
||||||
'sort': BookmarkSearch.SORT_TITLE_DESC,
|
)
|
||||||
'shared': BookmarkSearch.FILTER_SHARED_OFF,
|
self.assertEqual(
|
||||||
'unread': BookmarkSearch.FILTER_UNREAD_YES,
|
bookmark_search.preferences_dict,
|
||||||
})
|
{
|
||||||
|
"sort": BookmarkSearch.SORT_TITLE_DESC,
|
||||||
|
"shared": BookmarkSearch.FILTER_SHARED_OFF,
|
||||||
|
"unread": BookmarkSearch.FILTER_UNREAD_YES,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
# only returns preferences
|
# only returns preferences
|
||||||
bookmark_search = BookmarkSearch(q='search query', user='user123')
|
bookmark_search = BookmarkSearch(q="search query", user="user123")
|
||||||
self.assertEqual(bookmark_search.preferences_dict, {
|
self.assertEqual(
|
||||||
'sort': BookmarkSearch.SORT_ADDED_DESC,
|
bookmark_search.preferences_dict,
|
||||||
'shared': BookmarkSearch.FILTER_SHARED_OFF,
|
{
|
||||||
'unread': BookmarkSearch.FILTER_UNREAD_OFF,
|
"sort": BookmarkSearch.SORT_ADDED_DESC,
|
||||||
})
|
"shared": BookmarkSearch.FILTER_SHARED_OFF,
|
||||||
|
"unread": BookmarkSearch.FILTER_UNREAD_OFF,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
|
@ -8,21 +8,25 @@ from bookmarks.tests.helpers import BookmarkFactoryMixin, HtmlTestMixin
|
||||||
|
|
||||||
|
|
||||||
class BookmarkSearchTagTest(TestCase, 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()
|
rf = RequestFactory()
|
||||||
request = rf.get(url)
|
request = rf.get(url)
|
||||||
request.user = self.get_or_create_test_user()
|
request.user = self.get_or_create_test_user()
|
||||||
request.user_profile = self.get_or_create_test_user().profile
|
request.user_profile = self.get_or_create_test_user().profile
|
||||||
search = BookmarkSearch.from_request(request.GET)
|
search = BookmarkSearch.from_request(request.GET)
|
||||||
context = RequestContext(request, {
|
context = RequestContext(
|
||||||
'request': request,
|
request,
|
||||||
'search': search,
|
{
|
||||||
'tags': tags,
|
"request": request,
|
||||||
'mode': mode,
|
"search": search,
|
||||||
})
|
"tags": tags,
|
||||||
|
"mode": mode,
|
||||||
|
},
|
||||||
|
)
|
||||||
template_to_render = Template(
|
template_to_render = Template(
|
||||||
'{% load bookmarks %}'
|
"{% load bookmarks %}" "{% bookmark_search search tags mode %}"
|
||||||
'{% bookmark_search search tags mode %}'
|
|
||||||
)
|
)
|
||||||
return template_to_render.render(context)
|
return template_to_render.render(context)
|
||||||
|
|
||||||
|
@ -31,7 +35,7 @@ class BookmarkSearchTagTest(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
|
||||||
self.assertIsNotNone(input)
|
self.assertIsNotNone(input)
|
||||||
|
|
||||||
if value is not None:
|
if value is not None:
|
||||||
self.assertEqual(input['value'], value)
|
self.assertEqual(input["value"], value)
|
||||||
|
|
||||||
def assertNoHiddenInput(self, form: BeautifulSoup, name: str):
|
def assertNoHiddenInput(self, form: BeautifulSoup, name: str):
|
||||||
input = form.select_one(f'input[name="{name}"][type="hidden"]')
|
input = form.select_one(f'input[name="{name}"][type="hidden"]')
|
||||||
|
@ -42,19 +46,19 @@ class BookmarkSearchTagTest(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
|
||||||
self.assertIsNotNone(input)
|
self.assertIsNotNone(input)
|
||||||
|
|
||||||
if value is not None:
|
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):
|
def assertSelect(self, form: BeautifulSoup, name: str, value: str = None):
|
||||||
select = form.select_one(f'select[name="{name}"]')
|
select = form.select_one(f'select[name="{name}"]')
|
||||||
self.assertIsNotNone(select)
|
self.assertIsNotNone(select)
|
||||||
|
|
||||||
if value is not None:
|
if value is not None:
|
||||||
options = select.select('option')
|
options = select.select("option")
|
||||||
for option in options:
|
for option in options:
|
||||||
if option['value'] == value:
|
if option["value"] == value:
|
||||||
self.assertTrue(option.has_attr('selected'))
|
self.assertTrue(option.has_attr("selected"))
|
||||||
else:
|
else:
|
||||||
self.assertFalse(option.has_attr('selected'))
|
self.assertFalse(option.has_attr("selected"))
|
||||||
|
|
||||||
def assertRadioGroup(self, form: BeautifulSoup, name: str, value: str = None):
|
def assertRadioGroup(self, form: BeautifulSoup, name: str, value: str = None):
|
||||||
radios = form.select(f'input[name="{name}"][type="radio"]')
|
radios = form.select(f'input[name="{name}"][type="radio"]')
|
||||||
|
@ -62,165 +66,182 @@ class BookmarkSearchTagTest(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
|
||||||
|
|
||||||
if value is not None:
|
if value is not None:
|
||||||
for radio in radios:
|
for radio in radios:
|
||||||
if radio['value'] == value:
|
if radio["value"] == value:
|
||||||
self.assertTrue(radio.has_attr('checked'))
|
self.assertTrue(radio.has_attr("checked"))
|
||||||
else:
|
else:
|
||||||
self.assertFalse(radio.has_attr('checked'))
|
self.assertFalse(radio.has_attr("checked"))
|
||||||
|
|
||||||
def assertNoRadioGroup(self, form: BeautifulSoup, name: str):
|
def assertNoRadioGroup(self, form: BeautifulSoup, name: str):
|
||||||
radios = form.select(f'input[name="{name}"][type="radio"]')
|
radios = form.select(f'input[name="{name}"][type="radio"]')
|
||||||
self.assertTrue(len(radios) == 0)
|
self.assertTrue(len(radios) == 0)
|
||||||
|
|
||||||
def assertUnmodifiedLabel(self, html: str, text: str, id: str = ''):
|
def assertUnmodifiedLabel(self, html: str, text: str, id: str = ""):
|
||||||
id_attr = f'for="{id}"' if id else ''
|
id_attr = f'for="{id}"' if id else ""
|
||||||
tag = 'label' if id else 'div'
|
tag = "label" if id else "div"
|
||||||
needle = f'<{tag} class="form-label" {id_attr}>{text}</{tag}>'
|
needle = f'<{tag} class="form-label" {id_attr}>{text}</{tag}>'
|
||||||
|
|
||||||
self.assertInHTML(needle, html)
|
self.assertInHTML(needle, html)
|
||||||
|
|
||||||
def assertModifiedLabel(self, html: str, text: str, id: str = ''):
|
def assertModifiedLabel(self, html: str, text: str, id: str = ""):
|
||||||
id_attr = f'for="{id}"' if id else ''
|
id_attr = f'for="{id}"' if id else ""
|
||||||
tag = 'label' if id else 'div'
|
tag = "label" if id else "div"
|
||||||
needle = f'<{tag} class="form-label text-bold" {id_attr}>{text}</{tag}>'
|
needle = f'<{tag} class="form-label text-bold" {id_attr}>{text}</{tag}>'
|
||||||
|
|
||||||
self.assertInHTML(needle, html)
|
self.assertInHTML(needle, html)
|
||||||
|
|
||||||
def test_search_form_inputs(self):
|
def test_search_form_inputs(self):
|
||||||
# Without params
|
# Without params
|
||||||
url = '/test'
|
url = "/test"
|
||||||
rendered_template = self.render_template(url)
|
rendered_template = self.render_template(url)
|
||||||
soup = self.make_soup(rendered_template)
|
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.assertSearchInput(search_form, "q")
|
||||||
self.assertNoHiddenInput(search_form, 'user')
|
self.assertNoHiddenInput(search_form, "user")
|
||||||
self.assertNoHiddenInput(search_form, 'sort')
|
self.assertNoHiddenInput(search_form, "sort")
|
||||||
self.assertNoHiddenInput(search_form, 'shared')
|
self.assertNoHiddenInput(search_form, "shared")
|
||||||
self.assertNoHiddenInput(search_form, 'unread')
|
self.assertNoHiddenInput(search_form, "unread")
|
||||||
|
|
||||||
# With params
|
# 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)
|
rendered_template = self.render_template(url)
|
||||||
soup = self.make_soup(rendered_template)
|
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.assertSearchInput(search_form, "q", "foo")
|
||||||
self.assertHiddenInput(search_form, 'user', 'john')
|
self.assertHiddenInput(search_form, "user", "john")
|
||||||
self.assertHiddenInput(search_form, 'sort', BookmarkSearch.SORT_TITLE_ASC)
|
self.assertHiddenInput(search_form, "sort", BookmarkSearch.SORT_TITLE_ASC)
|
||||||
self.assertHiddenInput(search_form, 'shared', BookmarkSearch.FILTER_SHARED_SHARED)
|
self.assertHiddenInput(
|
||||||
self.assertHiddenInput(search_form, 'unread', BookmarkSearch.FILTER_UNREAD_YES)
|
search_form, "shared", BookmarkSearch.FILTER_SHARED_SHARED
|
||||||
|
)
|
||||||
|
self.assertHiddenInput(search_form, "unread", BookmarkSearch.FILTER_UNREAD_YES)
|
||||||
|
|
||||||
def test_preferences_form_inputs(self):
|
def test_preferences_form_inputs(self):
|
||||||
# Without params
|
# Without params
|
||||||
url = '/test'
|
url = "/test"
|
||||||
rendered_template = self.render_template(url)
|
rendered_template = self.render_template(url)
|
||||||
soup = self.make_soup(rendered_template)
|
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, "q")
|
||||||
self.assertNoHiddenInput(preferences_form, 'user')
|
self.assertNoHiddenInput(preferences_form, "user")
|
||||||
self.assertNoHiddenInput(preferences_form, 'sort')
|
self.assertNoHiddenInput(preferences_form, "sort")
|
||||||
self.assertNoHiddenInput(preferences_form, 'shared')
|
self.assertNoHiddenInput(preferences_form, "shared")
|
||||||
self.assertNoHiddenInput(preferences_form, 'unread')
|
self.assertNoHiddenInput(preferences_form, "unread")
|
||||||
|
|
||||||
self.assertSelect(preferences_form, 'sort', BookmarkSearch.SORT_ADDED_DESC)
|
self.assertSelect(preferences_form, "sort", BookmarkSearch.SORT_ADDED_DESC)
|
||||||
self.assertRadioGroup(preferences_form, 'shared', BookmarkSearch.FILTER_SHARED_OFF)
|
self.assertRadioGroup(
|
||||||
self.assertRadioGroup(preferences_form, 'unread', BookmarkSearch.FILTER_UNREAD_OFF)
|
preferences_form, "shared", BookmarkSearch.FILTER_SHARED_OFF
|
||||||
|
)
|
||||||
|
self.assertRadioGroup(
|
||||||
|
preferences_form, "unread", BookmarkSearch.FILTER_UNREAD_OFF
|
||||||
|
)
|
||||||
|
|
||||||
# With params
|
# 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)
|
rendered_template = self.render_template(url)
|
||||||
soup = self.make_soup(rendered_template)
|
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, "q", "foo")
|
||||||
self.assertHiddenInput(preferences_form, 'user', 'john')
|
self.assertHiddenInput(preferences_form, "user", "john")
|
||||||
self.assertNoHiddenInput(preferences_form, 'sort')
|
self.assertNoHiddenInput(preferences_form, "sort")
|
||||||
self.assertNoHiddenInput(preferences_form, 'shared')
|
self.assertNoHiddenInput(preferences_form, "shared")
|
||||||
self.assertNoHiddenInput(preferences_form, 'unread')
|
self.assertNoHiddenInput(preferences_form, "unread")
|
||||||
|
|
||||||
self.assertSelect(preferences_form, 'sort', BookmarkSearch.SORT_TITLE_ASC)
|
self.assertSelect(preferences_form, "sort", BookmarkSearch.SORT_TITLE_ASC)
|
||||||
self.assertRadioGroup(preferences_form, 'shared', BookmarkSearch.FILTER_SHARED_SHARED)
|
self.assertRadioGroup(
|
||||||
self.assertRadioGroup(preferences_form, 'unread', BookmarkSearch.FILTER_UNREAD_YES)
|
preferences_form, "shared", BookmarkSearch.FILTER_SHARED_SHARED
|
||||||
|
)
|
||||||
|
self.assertRadioGroup(
|
||||||
|
preferences_form, "unread", BookmarkSearch.FILTER_UNREAD_YES
|
||||||
|
)
|
||||||
|
|
||||||
def test_preferences_form_inputs_shared_mode(self):
|
def test_preferences_form_inputs_shared_mode(self):
|
||||||
# Without params
|
# Without params
|
||||||
url = '/test'
|
url = "/test"
|
||||||
rendered_template = self.render_template(url, mode='shared')
|
rendered_template = self.render_template(url, mode="shared")
|
||||||
soup = self.make_soup(rendered_template)
|
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, "q")
|
||||||
self.assertNoHiddenInput(preferences_form, 'user')
|
self.assertNoHiddenInput(preferences_form, "user")
|
||||||
self.assertNoHiddenInput(preferences_form, 'sort')
|
self.assertNoHiddenInput(preferences_form, "sort")
|
||||||
self.assertNoHiddenInput(preferences_form, 'shared')
|
self.assertNoHiddenInput(preferences_form, "shared")
|
||||||
self.assertNoHiddenInput(preferences_form, 'unread')
|
self.assertNoHiddenInput(preferences_form, "unread")
|
||||||
|
|
||||||
self.assertSelect(preferences_form, 'sort', BookmarkSearch.SORT_ADDED_DESC)
|
self.assertSelect(preferences_form, "sort", BookmarkSearch.SORT_ADDED_DESC)
|
||||||
self.assertNoRadioGroup(preferences_form, 'shared')
|
self.assertNoRadioGroup(preferences_form, "shared")
|
||||||
self.assertNoRadioGroup(preferences_form, 'unread')
|
self.assertNoRadioGroup(preferences_form, "unread")
|
||||||
|
|
||||||
# With params
|
# With params
|
||||||
url = '/test?q=foo&user=john&sort=title_asc'
|
url = "/test?q=foo&user=john&sort=title_asc"
|
||||||
rendered_template = self.render_template(url, mode='shared')
|
rendered_template = self.render_template(url, mode="shared")
|
||||||
soup = self.make_soup(rendered_template)
|
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, "q", "foo")
|
||||||
self.assertHiddenInput(preferences_form, 'user', 'john')
|
self.assertHiddenInput(preferences_form, "user", "john")
|
||||||
self.assertNoHiddenInput(preferences_form, 'sort')
|
self.assertNoHiddenInput(preferences_form, "sort")
|
||||||
self.assertNoHiddenInput(preferences_form, 'shared')
|
self.assertNoHiddenInput(preferences_form, "shared")
|
||||||
self.assertNoHiddenInput(preferences_form, 'unread')
|
self.assertNoHiddenInput(preferences_form, "unread")
|
||||||
|
|
||||||
self.assertSelect(preferences_form, 'sort', BookmarkSearch.SORT_TITLE_ASC)
|
self.assertSelect(preferences_form, "sort", BookmarkSearch.SORT_TITLE_ASC)
|
||||||
self.assertNoRadioGroup(preferences_form, 'shared')
|
self.assertNoRadioGroup(preferences_form, "shared")
|
||||||
self.assertNoRadioGroup(preferences_form, 'unread')
|
self.assertNoRadioGroup(preferences_form, "unread")
|
||||||
|
|
||||||
def test_modified_indicator(self):
|
def test_modified_indicator(self):
|
||||||
# Without modifications
|
# Without modifications
|
||||||
url = '/test'
|
url = "/test"
|
||||||
rendered_template = self.render_template(url)
|
rendered_template = self.render_template(url)
|
||||||
|
|
||||||
self.assertIn('<button type="button" class="btn dropdown-toggle">', rendered_template)
|
self.assertIn(
|
||||||
|
'<button type="button" class="btn dropdown-toggle">', rendered_template
|
||||||
|
)
|
||||||
|
|
||||||
# With modifications
|
# With modifications
|
||||||
url = '/test?sort=title_asc'
|
url = "/test?sort=title_asc"
|
||||||
rendered_template = self.render_template(url)
|
rendered_template = self.render_template(url)
|
||||||
|
|
||||||
self.assertIn('<button type="button" class="btn dropdown-toggle badge">', rendered_template)
|
self.assertIn(
|
||||||
|
'<button type="button" class="btn dropdown-toggle badge">',
|
||||||
|
rendered_template,
|
||||||
|
)
|
||||||
|
|
||||||
# Ignores non-preferences modifications
|
# Ignores non-preferences modifications
|
||||||
url = '/test?q=foo&user=john'
|
url = "/test?q=foo&user=john"
|
||||||
rendered_template = self.render_template(url)
|
rendered_template = self.render_template(url)
|
||||||
|
|
||||||
self.assertIn('<button type="button" class="btn dropdown-toggle">', rendered_template)
|
self.assertIn(
|
||||||
|
'<button type="button" class="btn dropdown-toggle">', rendered_template
|
||||||
|
)
|
||||||
|
|
||||||
def test_modified_labels(self):
|
def test_modified_labels(self):
|
||||||
# Without modifications
|
# Without modifications
|
||||||
url = '/test'
|
url = "/test"
|
||||||
rendered_template = self.render_template(url)
|
rendered_template = self.render_template(url)
|
||||||
|
|
||||||
self.assertUnmodifiedLabel(rendered_template, 'Sort by', 'id_sort')
|
self.assertUnmodifiedLabel(rendered_template, "Sort by", "id_sort")
|
||||||
self.assertUnmodifiedLabel(rendered_template, 'Shared filter')
|
self.assertUnmodifiedLabel(rendered_template, "Shared filter")
|
||||||
self.assertUnmodifiedLabel(rendered_template, 'Unread filter')
|
self.assertUnmodifiedLabel(rendered_template, "Unread filter")
|
||||||
|
|
||||||
# Modified sort
|
# Modified sort
|
||||||
url = '/test?sort=title_asc'
|
url = "/test?sort=title_asc"
|
||||||
rendered_template = self.render_template(url)
|
rendered_template = self.render_template(url)
|
||||||
self.assertModifiedLabel(rendered_template, 'Sort by', 'id_sort')
|
self.assertModifiedLabel(rendered_template, "Sort by", "id_sort")
|
||||||
self.assertUnmodifiedLabel(rendered_template, 'Shared filter')
|
self.assertUnmodifiedLabel(rendered_template, "Shared filter")
|
||||||
self.assertUnmodifiedLabel(rendered_template, 'Unread filter')
|
self.assertUnmodifiedLabel(rendered_template, "Unread filter")
|
||||||
|
|
||||||
# Modified shared
|
# Modified shared
|
||||||
url = '/test?shared=yes'
|
url = "/test?shared=yes"
|
||||||
rendered_template = self.render_template(url)
|
rendered_template = self.render_template(url)
|
||||||
self.assertUnmodifiedLabel(rendered_template, 'Sort by', 'id_sort')
|
self.assertUnmodifiedLabel(rendered_template, "Sort by", "id_sort")
|
||||||
self.assertModifiedLabel(rendered_template, 'Shared filter')
|
self.assertModifiedLabel(rendered_template, "Shared filter")
|
||||||
self.assertUnmodifiedLabel(rendered_template, 'Unread filter')
|
self.assertUnmodifiedLabel(rendered_template, "Unread filter")
|
||||||
|
|
||||||
# Modified unread
|
# Modified unread
|
||||||
url = '/test?unread=yes'
|
url = "/test?unread=yes"
|
||||||
rendered_template = self.render_template(url)
|
rendered_template = self.render_template(url)
|
||||||
self.assertUnmodifiedLabel(rendered_template, 'Sort by', 'id_sort')
|
self.assertUnmodifiedLabel(rendered_template, "Sort by", "id_sort")
|
||||||
self.assertUnmodifiedLabel(rendered_template, 'Shared filter')
|
self.assertUnmodifiedLabel(rendered_template, "Shared filter")
|
||||||
self.assertModifiedLabel(rendered_template, 'Unread filter')
|
self.assertModifiedLabel(rendered_template, "Unread filter")
|
||||||
|
|
|
@ -15,39 +15,50 @@ class BookmarkSharedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
|
||||||
user = self.get_or_create_test_user()
|
user = self.get_or_create_test_user()
|
||||||
self.client.force_login(user)
|
self.client.force_login(user)
|
||||||
|
|
||||||
def assertBookmarkCount(self, html: str, bookmark: Bookmark, count: int, link_target: str = '_blank'):
|
def assertBookmarkCount(
|
||||||
|
self, html: str, bookmark: Bookmark, count: int, link_target: str = "_blank"
|
||||||
|
):
|
||||||
self.assertInHTML(
|
self.assertInHTML(
|
||||||
f'<a href="{bookmark.url}" target="{link_target}" rel="noopener">{bookmark.resolved_title}</a>',
|
f'<a href="{bookmark.url}" target="{link_target}" rel="noopener">{bookmark.resolved_title}</a>',
|
||||||
html, count=count
|
html,
|
||||||
|
count=count,
|
||||||
)
|
)
|
||||||
|
|
||||||
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())
|
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)
|
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))
|
self.assertEqual(len(bookmark_items), len(bookmarks))
|
||||||
|
|
||||||
for bookmark in bookmarks:
|
for bookmark in bookmarks:
|
||||||
bookmark_item = bookmark_list.select_one(
|
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)
|
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())
|
soup = self.make_soup(response.content.decode())
|
||||||
|
|
||||||
for bookmark in bookmarks:
|
for bookmark in bookmarks:
|
||||||
bookmark_item = soup.select_one(
|
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)
|
self.assertIsNone(bookmark_item)
|
||||||
|
|
||||||
def assertVisibleTags(self, response, tags: List[Tag]):
|
def assertVisibleTags(self, response, tags: List[Tag]):
|
||||||
soup = self.make_soup(response.content.decode())
|
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)
|
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))
|
self.assertEqual(len(tag_items), len(tags))
|
||||||
|
|
||||||
tag_item_names = [tag_item.text.strip() for tag_item in tag_items]
|
tag_item_names = [tag_item.text.strip() for tag_item in tag_items]
|
||||||
|
@ -57,7 +68,7 @@ class BookmarkSharedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
|
||||||
|
|
||||||
def assertInvisibleTags(self, response, tags: List[Tag]):
|
def assertInvisibleTags(self, response, tags: List[Tag]):
|
||||||
soup = self.make_soup(response.content.decode())
|
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]
|
tag_item_names = [tag_item.text.strip() for tag_item in tag_items]
|
||||||
|
|
||||||
|
@ -67,26 +78,31 @@ class BookmarkSharedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
|
||||||
def assertVisibleUserOptions(self, response, users: List[User]):
|
def assertVisibleUserOptions(self, response, users: List[User]):
|
||||||
html = response.content.decode()
|
html = response.content.decode()
|
||||||
|
|
||||||
user_options = [
|
user_options = ['<option value="" selected="">Everyone</option>']
|
||||||
'<option value="" selected="">Everyone</option>'
|
|
||||||
]
|
|
||||||
for user in users:
|
for user in users:
|
||||||
user_options.append(f'<option value="{user.username}">{user.username}</option>')
|
user_options.append(
|
||||||
user_select_html = f'''
|
f'<option value="{user.username}">{user.username}</option>'
|
||||||
|
)
|
||||||
|
user_select_html = f"""
|
||||||
<select name="user" class="form-select" required="" id="id_user">
|
<select name="user" class="form-select" required="" id="id_user">
|
||||||
{''.join(user_options)}
|
{''.join(user_options)}
|
||||||
</select>
|
</select>
|
||||||
'''
|
"""
|
||||||
|
|
||||||
self.assertInHTML(user_select_html, html)
|
self.assertInHTML(user_select_html, html)
|
||||||
|
|
||||||
def assertEditLink(self, response, url):
|
def assertEditLink(self, response, url):
|
||||||
html = response.content.decode()
|
html = response.content.decode()
|
||||||
self.assertInHTML(f'''
|
self.assertInHTML(
|
||||||
|
f"""
|
||||||
<a href="{url}">Edit</a>
|
<a href="{url}">Edit</a>
|
||||||
''', html)
|
""",
|
||||||
|
html,
|
||||||
|
)
|
||||||
|
|
||||||
def test_should_list_shared_bookmarks_from_all_users_that_have_sharing_enabled(self):
|
def test_should_list_shared_bookmarks_from_all_users_that_have_sharing_enabled(
|
||||||
|
self,
|
||||||
|
):
|
||||||
self.authenticate()
|
self.authenticate()
|
||||||
user1 = self.setup_user(enable_sharing=True)
|
user1 = self.setup_user(enable_sharing=True)
|
||||||
user2 = self.setup_user(enable_sharing=True)
|
user2 = self.setup_user(enable_sharing=True)
|
||||||
|
@ -105,7 +121,7 @@ class BookmarkSharedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
|
||||||
self.setup_bookmark(shared=True, user=user4),
|
self.setup_bookmark(shared=True, user=user4),
|
||||||
]
|
]
|
||||||
|
|
||||||
response = self.client.get(reverse('bookmarks:shared'))
|
response = self.client.get(reverse("bookmarks:shared"))
|
||||||
|
|
||||||
self.assertVisibleBookmarks(response, visible_bookmarks)
|
self.assertVisibleBookmarks(response, visible_bookmarks)
|
||||||
self.assertInvisibleBookmarks(response, invisible_bookmarks)
|
self.assertInvisibleBookmarks(response, invisible_bookmarks)
|
||||||
|
@ -124,7 +140,7 @@ class BookmarkSharedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
|
||||||
self.setup_bookmark(shared=True, user=user3),
|
self.setup_bookmark(shared=True, user=user3),
|
||||||
]
|
]
|
||||||
|
|
||||||
url = reverse('bookmarks:shared') + '?user=' + user1.username
|
url = reverse("bookmarks:shared") + "?user=" + user1.username
|
||||||
response = self.client.get(url)
|
response = self.client.get(url)
|
||||||
|
|
||||||
self.assertVisibleBookmarks(response, visible_bookmarks)
|
self.assertVisibleBookmarks(response, visible_bookmarks)
|
||||||
|
@ -134,10 +150,12 @@ class BookmarkSharedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
|
||||||
self.authenticate()
|
self.authenticate()
|
||||||
user = self.setup_user(enable_sharing=True)
|
user = self.setup_user(enable_sharing=True)
|
||||||
|
|
||||||
visible_bookmarks = self.setup_numbered_bookmarks(3, shared=True, user=user, prefix='foo')
|
visible_bookmarks = self.setup_numbered_bookmarks(
|
||||||
|
3, shared=True, user=user, prefix="foo"
|
||||||
|
)
|
||||||
invisible_bookmarks = self.setup_numbered_bookmarks(3, shared=True, user=user)
|
invisible_bookmarks = self.setup_numbered_bookmarks(3, shared=True, user=user)
|
||||||
|
|
||||||
response = self.client.get(reverse('bookmarks:shared') + '?q=foo')
|
response = self.client.get(reverse("bookmarks:shared") + "?q=foo")
|
||||||
|
|
||||||
self.assertVisibleBookmarks(response, visible_bookmarks)
|
self.assertVisibleBookmarks(response, visible_bookmarks)
|
||||||
self.assertInvisibleBookmarks(response, invisible_bookmarks)
|
self.assertInvisibleBookmarks(response, invisible_bookmarks)
|
||||||
|
@ -146,15 +164,21 @@ class BookmarkSharedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
|
||||||
user1 = self.setup_user(enable_sharing=True, enable_public_sharing=True)
|
user1 = self.setup_user(enable_sharing=True, enable_public_sharing=True)
|
||||||
user2 = self.setup_user(enable_sharing=True)
|
user2 = self.setup_user(enable_sharing=True)
|
||||||
|
|
||||||
visible_bookmarks = self.setup_numbered_bookmarks(3, shared=True, user=user1, prefix='user1')
|
visible_bookmarks = self.setup_numbered_bookmarks(
|
||||||
invisible_bookmarks = self.setup_numbered_bookmarks(3, shared=True, user=user2, prefix='user2')
|
3, shared=True, user=user1, prefix="user1"
|
||||||
|
)
|
||||||
|
invisible_bookmarks = self.setup_numbered_bookmarks(
|
||||||
|
3, shared=True, user=user2, prefix="user2"
|
||||||
|
)
|
||||||
|
|
||||||
response = self.client.get(reverse('bookmarks:shared'))
|
response = self.client.get(reverse("bookmarks:shared"))
|
||||||
|
|
||||||
self.assertVisibleBookmarks(response, visible_bookmarks)
|
self.assertVisibleBookmarks(response, visible_bookmarks)
|
||||||
self.assertInvisibleBookmarks(response, invisible_bookmarks)
|
self.assertInvisibleBookmarks(response, invisible_bookmarks)
|
||||||
|
|
||||||
def test_should_list_tags_for_shared_bookmarks_from_all_users_that_have_sharing_enabled(self):
|
def test_should_list_tags_for_shared_bookmarks_from_all_users_that_have_sharing_enabled(
|
||||||
|
self,
|
||||||
|
):
|
||||||
self.authenticate()
|
self.authenticate()
|
||||||
user1 = self.setup_user(enable_sharing=True)
|
user1 = self.setup_user(enable_sharing=True)
|
||||||
user2 = self.setup_user(enable_sharing=True)
|
user2 = self.setup_user(enable_sharing=True)
|
||||||
|
@ -181,7 +205,7 @@ class BookmarkSharedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
|
||||||
self.setup_bookmark(shared=False, user=user3, tags=[invisible_tags[2]])
|
self.setup_bookmark(shared=False, user=user3, tags=[invisible_tags[2]])
|
||||||
self.setup_bookmark(shared=True, user=user4, tags=[invisible_tags[3]])
|
self.setup_bookmark(shared=True, user=user4, tags=[invisible_tags[3]])
|
||||||
|
|
||||||
response = self.client.get(reverse('bookmarks:shared'))
|
response = self.client.get(reverse("bookmarks:shared"))
|
||||||
|
|
||||||
self.assertVisibleTags(response, visible_tags)
|
self.assertVisibleTags(response, visible_tags)
|
||||||
self.assertInvisibleTags(response, invisible_tags)
|
self.assertInvisibleTags(response, invisible_tags)
|
||||||
|
@ -203,7 +227,7 @@ class BookmarkSharedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
|
||||||
self.setup_bookmark(shared=True, user=user2, tags=[invisible_tags[0]])
|
self.setup_bookmark(shared=True, user=user2, tags=[invisible_tags[0]])
|
||||||
self.setup_bookmark(shared=True, user=user3, tags=[invisible_tags[1]])
|
self.setup_bookmark(shared=True, user=user3, tags=[invisible_tags[1]])
|
||||||
|
|
||||||
url = reverse('bookmarks:shared') + '?user=' + user1.username
|
url = reverse("bookmarks:shared") + "?user=" + user1.username
|
||||||
response = self.client.get(url)
|
response = self.client.get(url)
|
||||||
|
|
||||||
self.assertVisibleTags(response, visible_tags)
|
self.assertVisibleTags(response, visible_tags)
|
||||||
|
@ -225,15 +249,21 @@ class BookmarkSharedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
|
||||||
self.setup_tag(user=user3),
|
self.setup_tag(user=user3),
|
||||||
]
|
]
|
||||||
|
|
||||||
self.setup_bookmark(shared=True, user=user1, title='searchvalue', tags=[visible_tags[0]])
|
self.setup_bookmark(
|
||||||
self.setup_bookmark(shared=True, user=user2, title='searchvalue', tags=[visible_tags[1]])
|
shared=True, user=user1, title="searchvalue", tags=[visible_tags[0]]
|
||||||
self.setup_bookmark(shared=True, user=user3, title='searchvalue', tags=[visible_tags[2]])
|
)
|
||||||
|
self.setup_bookmark(
|
||||||
|
shared=True, user=user2, title="searchvalue", tags=[visible_tags[1]]
|
||||||
|
)
|
||||||
|
self.setup_bookmark(
|
||||||
|
shared=True, user=user3, title="searchvalue", tags=[visible_tags[2]]
|
||||||
|
)
|
||||||
|
|
||||||
self.setup_bookmark(shared=True, user=user1, tags=[invisible_tags[0]])
|
self.setup_bookmark(shared=True, user=user1, tags=[invisible_tags[0]])
|
||||||
self.setup_bookmark(shared=True, user=user2, tags=[invisible_tags[1]])
|
self.setup_bookmark(shared=True, user=user2, tags=[invisible_tags[1]])
|
||||||
self.setup_bookmark(shared=True, user=user3, tags=[invisible_tags[2]])
|
self.setup_bookmark(shared=True, user=user3, tags=[invisible_tags[2]])
|
||||||
|
|
||||||
response = self.client.get(reverse('bookmarks:shared') + '?q=searchvalue')
|
response = self.client.get(reverse("bookmarks:shared") + "?q=searchvalue")
|
||||||
|
|
||||||
self.assertVisibleTags(response, visible_tags)
|
self.assertVisibleTags(response, visible_tags)
|
||||||
self.assertInvisibleTags(response, invisible_tags)
|
self.assertInvisibleTags(response, invisible_tags)
|
||||||
|
@ -257,7 +287,7 @@ class BookmarkSharedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
|
||||||
self.setup_bookmark(shared=True, user=user2, tags=[invisible_tags[0]])
|
self.setup_bookmark(shared=True, user=user2, tags=[invisible_tags[0]])
|
||||||
self.setup_bookmark(shared=True, user=user2, tags=[invisible_tags[1]])
|
self.setup_bookmark(shared=True, user=user2, tags=[invisible_tags[1]])
|
||||||
|
|
||||||
response = self.client.get(reverse('bookmarks:shared'))
|
response = self.client.get(reverse("bookmarks:shared"))
|
||||||
|
|
||||||
self.assertVisibleTags(response, visible_tags)
|
self.assertVisibleTags(response, visible_tags)
|
||||||
self.assertInvisibleTags(response, invisible_tags)
|
self.assertInvisibleTags(response, invisible_tags)
|
||||||
|
@ -265,8 +295,8 @@ class BookmarkSharedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
|
||||||
def test_should_list_users_with_shared_bookmarks_if_sharing_is_enabled(self):
|
def test_should_list_users_with_shared_bookmarks_if_sharing_is_enabled(self):
|
||||||
self.authenticate()
|
self.authenticate()
|
||||||
expected_visible_users = [
|
expected_visible_users = [
|
||||||
self.setup_user(name='user_a', enable_sharing=True),
|
self.setup_user(name="user_a", enable_sharing=True),
|
||||||
self.setup_user(name='user_b', enable_sharing=True),
|
self.setup_user(name="user_b", enable_sharing=True),
|
||||||
]
|
]
|
||||||
self.setup_bookmark(shared=True, user=expected_visible_users[0])
|
self.setup_bookmark(shared=True, user=expected_visible_users[0])
|
||||||
self.setup_bookmark(shared=True, user=expected_visible_users[1])
|
self.setup_bookmark(shared=True, user=expected_visible_users[1])
|
||||||
|
@ -274,14 +304,18 @@ class BookmarkSharedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
|
||||||
self.setup_bookmark(shared=False, user=self.setup_user(enable_sharing=True))
|
self.setup_bookmark(shared=False, user=self.setup_user(enable_sharing=True))
|
||||||
self.setup_bookmark(shared=True, user=self.setup_user(enable_sharing=False))
|
self.setup_bookmark(shared=True, user=self.setup_user(enable_sharing=False))
|
||||||
|
|
||||||
response = self.client.get(reverse('bookmarks:shared'))
|
response = self.client.get(reverse("bookmarks:shared"))
|
||||||
self.assertVisibleUserOptions(response, expected_visible_users)
|
self.assertVisibleUserOptions(response, expected_visible_users)
|
||||||
|
|
||||||
def test_should_list_only_users_with_publicly_shared_bookmarks_without_login(self):
|
def test_should_list_only_users_with_publicly_shared_bookmarks_without_login(self):
|
||||||
# users with public sharing enabled
|
# users with public sharing enabled
|
||||||
expected_visible_users = [
|
expected_visible_users = [
|
||||||
self.setup_user(name='user_a', enable_sharing=True, enable_public_sharing=True),
|
self.setup_user(
|
||||||
self.setup_user(name='user_b', enable_sharing=True, enable_public_sharing=True),
|
name="user_a", enable_sharing=True, enable_public_sharing=True
|
||||||
|
),
|
||||||
|
self.setup_user(
|
||||||
|
name="user_b", enable_sharing=True, enable_public_sharing=True
|
||||||
|
),
|
||||||
]
|
]
|
||||||
self.setup_bookmark(shared=True, user=expected_visible_users[0])
|
self.setup_bookmark(shared=True, user=expected_visible_users[0])
|
||||||
self.setup_bookmark(shared=True, user=expected_visible_users[1])
|
self.setup_bookmark(shared=True, user=expected_visible_users[1])
|
||||||
|
@ -290,7 +324,7 @@ class BookmarkSharedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
|
||||||
self.setup_bookmark(shared=True, user=self.setup_user(enable_sharing=True))
|
self.setup_bookmark(shared=True, user=self.setup_user(enable_sharing=True))
|
||||||
self.setup_bookmark(shared=True, user=self.setup_user(enable_sharing=True))
|
self.setup_bookmark(shared=True, user=self.setup_user(enable_sharing=True))
|
||||||
|
|
||||||
response = self.client.get(reverse('bookmarks:shared'))
|
response = self.client.get(reverse("bookmarks:shared"))
|
||||||
self.assertVisibleUserOptions(response, expected_visible_users)
|
self.assertVisibleUserOptions(response, expected_visible_users)
|
||||||
|
|
||||||
def test_should_list_bookmarks_and_tags_for_search_preferences(self):
|
def test_should_list_bookmarks_and_tags_for_search_preferences(self):
|
||||||
|
@ -299,19 +333,33 @@ class BookmarkSharedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
|
||||||
|
|
||||||
user_profile = self.get_or_create_test_user().profile
|
user_profile = self.get_or_create_test_user().profile
|
||||||
user_profile.search_preferences = {
|
user_profile.search_preferences = {
|
||||||
'unread': BookmarkSearch.FILTER_UNREAD_YES,
|
"unread": BookmarkSearch.FILTER_UNREAD_YES,
|
||||||
}
|
}
|
||||||
user_profile.save()
|
user_profile.save()
|
||||||
|
|
||||||
unread_bookmarks = self.setup_numbered_bookmarks(3, shared=True, unread=True, with_tags=True, prefix='unread',
|
unread_bookmarks = self.setup_numbered_bookmarks(
|
||||||
tag_prefix='unread', user=other_user)
|
3,
|
||||||
read_bookmarks = self.setup_numbered_bookmarks(3, shared=True, unread=False, with_tags=True, prefix='read',
|
shared=True,
|
||||||
tag_prefix='read', user=other_user)
|
unread=True,
|
||||||
|
with_tags=True,
|
||||||
|
prefix="unread",
|
||||||
|
tag_prefix="unread",
|
||||||
|
user=other_user,
|
||||||
|
)
|
||||||
|
read_bookmarks = self.setup_numbered_bookmarks(
|
||||||
|
3,
|
||||||
|
shared=True,
|
||||||
|
unread=False,
|
||||||
|
with_tags=True,
|
||||||
|
prefix="read",
|
||||||
|
tag_prefix="read",
|
||||||
|
user=other_user,
|
||||||
|
)
|
||||||
|
|
||||||
unread_tags = self.get_tags_from_bookmarks(unread_bookmarks)
|
unread_tags = self.get_tags_from_bookmarks(unread_bookmarks)
|
||||||
read_tags = self.get_tags_from_bookmarks(read_bookmarks)
|
read_tags = self.get_tags_from_bookmarks(read_bookmarks)
|
||||||
|
|
||||||
response = self.client.get(reverse('bookmarks:shared'))
|
response = self.client.get(reverse("bookmarks:shared"))
|
||||||
self.assertVisibleBookmarks(response, unread_bookmarks)
|
self.assertVisibleBookmarks(response, unread_bookmarks)
|
||||||
self.assertInvisibleBookmarks(response, read_bookmarks)
|
self.assertInvisibleBookmarks(response, read_bookmarks)
|
||||||
self.assertVisibleTags(response, unread_tags)
|
self.assertVisibleTags(response, unread_tags)
|
||||||
|
@ -325,12 +373,12 @@ class BookmarkSharedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
|
||||||
visible_bookmarks = [
|
visible_bookmarks = [
|
||||||
self.setup_bookmark(shared=True),
|
self.setup_bookmark(shared=True),
|
||||||
self.setup_bookmark(shared=True),
|
self.setup_bookmark(shared=True),
|
||||||
self.setup_bookmark(shared=True)
|
self.setup_bookmark(shared=True),
|
||||||
]
|
]
|
||||||
|
|
||||||
response = self.client.get(reverse('bookmarks:shared'))
|
response = self.client.get(reverse("bookmarks:shared"))
|
||||||
|
|
||||||
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):
|
def test_should_open_bookmarks_in_same_page_if_specified_in_user_profile(self):
|
||||||
self.authenticate()
|
self.authenticate()
|
||||||
|
@ -342,12 +390,12 @@ class BookmarkSharedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
|
||||||
visible_bookmarks = [
|
visible_bookmarks = [
|
||||||
self.setup_bookmark(shared=True),
|
self.setup_bookmark(shared=True),
|
||||||
self.setup_bookmark(shared=True),
|
self.setup_bookmark(shared=True),
|
||||||
self.setup_bookmark(shared=True)
|
self.setup_bookmark(shared=True),
|
||||||
]
|
]
|
||||||
|
|
||||||
response = self.client.get(reverse('bookmarks:shared'))
|
response = self.client.get(reverse("bookmarks:shared"))
|
||||||
|
|
||||||
self.assertVisibleBookmarks(response, visible_bookmarks, '_self')
|
self.assertVisibleBookmarks(response, visible_bookmarks, "_self")
|
||||||
|
|
||||||
def test_edit_link_return_url_respects_search_options(self):
|
def test_edit_link_return_url_respects_search_options(self):
|
||||||
self.authenticate()
|
self.authenticate()
|
||||||
|
@ -355,180 +403,227 @@ class BookmarkSharedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
|
||||||
user.profile.enable_sharing = True
|
user.profile.enable_sharing = True
|
||||||
user.profile.save()
|
user.profile.save()
|
||||||
|
|
||||||
bookmark = self.setup_bookmark(title='foo', shared=True, user=user)
|
bookmark = self.setup_bookmark(title="foo", shared=True, user=user)
|
||||||
edit_url = reverse('bookmarks:edit', args=[bookmark.id])
|
edit_url = reverse("bookmarks:edit", args=[bookmark.id])
|
||||||
base_url = reverse('bookmarks:shared')
|
base_url = reverse("bookmarks:shared")
|
||||||
|
|
||||||
# without query params
|
# without query params
|
||||||
return_url = urllib.parse.quote(base_url)
|
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)
|
response = self.client.get(base_url)
|
||||||
self.assertEditLink(response, url)
|
self.assertEditLink(response, url)
|
||||||
|
|
||||||
# with query
|
# with query
|
||||||
url_params = '?q=foo'
|
url_params = "?q=foo"
|
||||||
return_url = urllib.parse.quote(base_url + url_params)
|
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)
|
response = self.client.get(base_url + url_params)
|
||||||
self.assertEditLink(response, url)
|
self.assertEditLink(response, url)
|
||||||
|
|
||||||
# with query and user
|
# with query and user
|
||||||
url_params = f'?q=foo&user={user.username}'
|
url_params = f"?q=foo&user={user.username}"
|
||||||
return_url = urllib.parse.quote(base_url + url_params)
|
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)
|
response = self.client.get(base_url + url_params)
|
||||||
self.assertEditLink(response, url)
|
self.assertEditLink(response, url)
|
||||||
|
|
||||||
# with query and sort and page
|
# 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)
|
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)
|
response = self.client.get(base_url + url_params)
|
||||||
self.assertEditLink(response, url)
|
self.assertEditLink(response, url)
|
||||||
|
|
||||||
def test_apply_search_preferences(self):
|
def test_apply_search_preferences(self):
|
||||||
# no params
|
# no params
|
||||||
response = self.client.post(reverse('bookmarks:shared'))
|
response = self.client.post(reverse("bookmarks:shared"))
|
||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
self.assertEqual(response.url, reverse('bookmarks:shared'))
|
self.assertEqual(response.url, reverse("bookmarks:shared"))
|
||||||
|
|
||||||
# some params
|
# some params
|
||||||
response = self.client.post(reverse('bookmarks:shared'), {
|
response = self.client.post(
|
||||||
'q': 'foo',
|
reverse("bookmarks:shared"),
|
||||||
'sort': BookmarkSearch.SORT_TITLE_ASC,
|
{
|
||||||
})
|
"q": "foo",
|
||||||
|
"sort": BookmarkSearch.SORT_TITLE_ASC,
|
||||||
|
},
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
self.assertEqual(response.url, reverse('bookmarks:shared') + '?q=foo&sort=title_asc')
|
self.assertEqual(
|
||||||
|
response.url, reverse("bookmarks:shared") + "?q=foo&sort=title_asc"
|
||||||
|
)
|
||||||
|
|
||||||
# params with default value are removed
|
# params with default value are removed
|
||||||
response = self.client.post(reverse('bookmarks:shared'), {
|
response = self.client.post(
|
||||||
'q': 'foo',
|
reverse("bookmarks:shared"),
|
||||||
'user': '',
|
{
|
||||||
'sort': BookmarkSearch.SORT_ADDED_DESC,
|
"q": "foo",
|
||||||
'shared': BookmarkSearch.FILTER_SHARED_OFF,
|
"user": "",
|
||||||
'unread': BookmarkSearch.FILTER_UNREAD_YES,
|
"sort": BookmarkSearch.SORT_ADDED_DESC,
|
||||||
})
|
"shared": BookmarkSearch.FILTER_SHARED_OFF,
|
||||||
|
"unread": BookmarkSearch.FILTER_UNREAD_YES,
|
||||||
|
},
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
self.assertEqual(response.url, reverse('bookmarks:shared') + '?q=foo&unread=yes')
|
self.assertEqual(
|
||||||
|
response.url, reverse("bookmarks:shared") + "?q=foo&unread=yes"
|
||||||
|
)
|
||||||
|
|
||||||
# page is removed
|
# page is removed
|
||||||
response = self.client.post(reverse('bookmarks:shared'), {
|
response = self.client.post(
|
||||||
'q': 'foo',
|
reverse("bookmarks:shared"),
|
||||||
'page': '2',
|
{
|
||||||
'sort': BookmarkSearch.SORT_TITLE_ASC,
|
"q": "foo",
|
||||||
})
|
"page": "2",
|
||||||
|
"sort": BookmarkSearch.SORT_TITLE_ASC,
|
||||||
|
},
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
self.assertEqual(response.url, reverse('bookmarks:shared') + '?q=foo&sort=title_asc')
|
self.assertEqual(
|
||||||
|
response.url, reverse("bookmarks:shared") + "?q=foo&sort=title_asc"
|
||||||
|
)
|
||||||
|
|
||||||
def test_save_search_preferences(self):
|
def test_save_search_preferences(self):
|
||||||
self.authenticate()
|
self.authenticate()
|
||||||
user_profile = self.user.profile
|
user_profile = self.user.profile
|
||||||
|
|
||||||
# no params
|
# no params
|
||||||
self.client.post(reverse('bookmarks:shared'), {
|
self.client.post(
|
||||||
'save': '',
|
reverse("bookmarks:shared"),
|
||||||
})
|
{
|
||||||
|
"save": "",
|
||||||
|
},
|
||||||
|
)
|
||||||
user_profile.refresh_from_db()
|
user_profile.refresh_from_db()
|
||||||
self.assertEqual(user_profile.search_preferences, {
|
self.assertEqual(
|
||||||
'sort': BookmarkSearch.SORT_ADDED_DESC,
|
user_profile.search_preferences,
|
||||||
'shared': BookmarkSearch.FILTER_SHARED_OFF,
|
{
|
||||||
'unread': BookmarkSearch.FILTER_UNREAD_OFF,
|
"sort": BookmarkSearch.SORT_ADDED_DESC,
|
||||||
})
|
"shared": BookmarkSearch.FILTER_SHARED_OFF,
|
||||||
|
"unread": BookmarkSearch.FILTER_UNREAD_OFF,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
# with param
|
# with param
|
||||||
self.client.post(reverse('bookmarks:shared'), {
|
self.client.post(
|
||||||
'save': '',
|
reverse("bookmarks:shared"),
|
||||||
'sort': BookmarkSearch.SORT_TITLE_ASC,
|
{
|
||||||
})
|
"save": "",
|
||||||
|
"sort": BookmarkSearch.SORT_TITLE_ASC,
|
||||||
|
},
|
||||||
|
)
|
||||||
user_profile.refresh_from_db()
|
user_profile.refresh_from_db()
|
||||||
self.assertEqual(user_profile.search_preferences, {
|
self.assertEqual(
|
||||||
'sort': BookmarkSearch.SORT_TITLE_ASC,
|
user_profile.search_preferences,
|
||||||
'shared': BookmarkSearch.FILTER_SHARED_OFF,
|
{
|
||||||
'unread': BookmarkSearch.FILTER_UNREAD_OFF,
|
"sort": BookmarkSearch.SORT_TITLE_ASC,
|
||||||
})
|
"shared": BookmarkSearch.FILTER_SHARED_OFF,
|
||||||
|
"unread": BookmarkSearch.FILTER_UNREAD_OFF,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
# add a param
|
# add a param
|
||||||
self.client.post(reverse('bookmarks:shared'), {
|
self.client.post(
|
||||||
'save': '',
|
reverse("bookmarks:shared"),
|
||||||
'sort': BookmarkSearch.SORT_TITLE_ASC,
|
{
|
||||||
'unread': BookmarkSearch.FILTER_UNREAD_YES,
|
"save": "",
|
||||||
})
|
"sort": BookmarkSearch.SORT_TITLE_ASC,
|
||||||
|
"unread": BookmarkSearch.FILTER_UNREAD_YES,
|
||||||
|
},
|
||||||
|
)
|
||||||
user_profile.refresh_from_db()
|
user_profile.refresh_from_db()
|
||||||
self.assertEqual(user_profile.search_preferences, {
|
self.assertEqual(
|
||||||
'sort': BookmarkSearch.SORT_TITLE_ASC,
|
user_profile.search_preferences,
|
||||||
'shared': BookmarkSearch.FILTER_SHARED_OFF,
|
{
|
||||||
'unread': BookmarkSearch.FILTER_UNREAD_YES,
|
"sort": BookmarkSearch.SORT_TITLE_ASC,
|
||||||
})
|
"shared": BookmarkSearch.FILTER_SHARED_OFF,
|
||||||
|
"unread": BookmarkSearch.FILTER_UNREAD_YES,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
# remove a param
|
# remove a param
|
||||||
self.client.post(reverse('bookmarks:shared'), {
|
self.client.post(
|
||||||
'save': '',
|
reverse("bookmarks:shared"),
|
||||||
'unread': BookmarkSearch.FILTER_UNREAD_YES,
|
{
|
||||||
})
|
"save": "",
|
||||||
|
"unread": BookmarkSearch.FILTER_UNREAD_YES,
|
||||||
|
},
|
||||||
|
)
|
||||||
user_profile.refresh_from_db()
|
user_profile.refresh_from_db()
|
||||||
self.assertEqual(user_profile.search_preferences, {
|
self.assertEqual(
|
||||||
'sort': BookmarkSearch.SORT_ADDED_DESC,
|
user_profile.search_preferences,
|
||||||
'shared': BookmarkSearch.FILTER_SHARED_OFF,
|
{
|
||||||
'unread': BookmarkSearch.FILTER_UNREAD_YES,
|
"sort": BookmarkSearch.SORT_ADDED_DESC,
|
||||||
})
|
"shared": BookmarkSearch.FILTER_SHARED_OFF,
|
||||||
|
"unread": BookmarkSearch.FILTER_UNREAD_YES,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
# ignores non-preferences
|
# ignores non-preferences
|
||||||
self.client.post(reverse('bookmarks:shared'), {
|
self.client.post(
|
||||||
'save': '',
|
reverse("bookmarks:shared"),
|
||||||
'q': 'foo',
|
{
|
||||||
'user': 'john',
|
"save": "",
|
||||||
'page': '3',
|
"q": "foo",
|
||||||
'sort': BookmarkSearch.SORT_TITLE_ASC,
|
"user": "john",
|
||||||
})
|
"page": "3",
|
||||||
|
"sort": BookmarkSearch.SORT_TITLE_ASC,
|
||||||
|
},
|
||||||
|
)
|
||||||
user_profile.refresh_from_db()
|
user_profile.refresh_from_db()
|
||||||
self.assertEqual(user_profile.search_preferences, {
|
self.assertEqual(
|
||||||
'sort': BookmarkSearch.SORT_TITLE_ASC,
|
user_profile.search_preferences,
|
||||||
'shared': BookmarkSearch.FILTER_SHARED_OFF,
|
{
|
||||||
'unread': BookmarkSearch.FILTER_UNREAD_OFF,
|
"sort": BookmarkSearch.SORT_TITLE_ASC,
|
||||||
})
|
"shared": BookmarkSearch.FILTER_SHARED_OFF,
|
||||||
|
"unread": BookmarkSearch.FILTER_UNREAD_OFF,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
def test_url_encode_bookmark_actions_url(self):
|
def test_url_encode_bookmark_actions_url(self):
|
||||||
url = reverse('bookmarks:shared') + '?q=%23foo'
|
url = reverse("bookmarks:shared") + "?q=%23foo"
|
||||||
response = self.client.get(url)
|
response = self.client.get(url)
|
||||||
html = response.content.decode()
|
html = response.content.decode()
|
||||||
soup = self.make_soup(html)
|
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'],
|
self.assertEqual(
|
||||||
'/bookmarks/shared/action?q=%23foo&return_url=%2Fbookmarks%2Fshared%3Fq%3D%2523foo')
|
actions_form.attrs["action"],
|
||||||
|
"/bookmarks/shared/action?q=%23foo&return_url=%2Fbookmarks%2Fshared%3Fq%3D%2523foo",
|
||||||
|
)
|
||||||
|
|
||||||
def test_encode_search_params(self):
|
def test_encode_search_params(self):
|
||||||
self.authenticate()
|
self.authenticate()
|
||||||
user = self.get_or_create_test_user()
|
user = self.get_or_create_test_user()
|
||||||
user.profile.enable_sharing = True
|
user.profile.enable_sharing = True
|
||||||
user.profile.save()
|
user.profile.save()
|
||||||
bookmark = self.setup_bookmark(description='alert(\'xss\')', shared=True)
|
bookmark = self.setup_bookmark(description="alert('xss')", shared=True)
|
||||||
|
|
||||||
url = reverse('bookmarks:shared') + '?q=alert(%27xss%27)'
|
url = reverse("bookmarks:shared") + "?q=alert(%27xss%27)"
|
||||||
response = self.client.get(url)
|
response = self.client.get(url)
|
||||||
self.assertNotContains(response, 'alert(\'xss\')')
|
self.assertNotContains(response, "alert('xss')")
|
||||||
self.assertContains(response, bookmark.url)
|
self.assertContains(response, bookmark.url)
|
||||||
|
|
||||||
url = reverse('bookmarks:shared') + '?sort=alert(%27xss%27)'
|
url = reverse("bookmarks:shared") + "?sort=alert(%27xss%27)"
|
||||||
response = self.client.get(url)
|
response = self.client.get(url)
|
||||||
self.assertNotContains(response, 'alert(\'xss\')')
|
self.assertNotContains(response, "alert('xss')")
|
||||||
|
|
||||||
url = reverse('bookmarks:shared') + '?unread=alert(%27xss%27)'
|
url = reverse("bookmarks:shared") + "?unread=alert(%27xss%27)"
|
||||||
response = self.client.get(url)
|
response = self.client.get(url)
|
||||||
self.assertNotContains(response, 'alert(\'xss\')')
|
self.assertNotContains(response, "alert('xss')")
|
||||||
|
|
||||||
url = reverse('bookmarks:shared') + '?shared=alert(%27xss%27)'
|
url = reverse("bookmarks:shared") + "?shared=alert(%27xss%27)"
|
||||||
response = self.client.get(url)
|
response = self.client.get(url)
|
||||||
self.assertNotContains(response, 'alert(\'xss\')')
|
self.assertNotContains(response, "alert('xss')")
|
||||||
|
|
||||||
url = reverse('bookmarks:shared') + '?user=alert(%27xss%27)'
|
url = reverse("bookmarks:shared") + "?user=alert(%27xss%27)"
|
||||||
response = self.client.get(url)
|
response = self.client.get(url)
|
||||||
self.assertNotContains(response, 'alert(\'xss\')')
|
self.assertNotContains(response, "alert('xss')")
|
||||||
|
|
||||||
url = reverse('bookmarks:shared') + '?page=alert(%27xss%27)'
|
url = reverse("bookmarks:shared") + "?page=alert(%27xss%27)"
|
||||||
response = self.client.get(url)
|
response = self.client.get(url)
|
||||||
self.assertNotContains(response, 'alert(\'xss\')')
|
self.assertNotContains(response, "alert('xss')")
|
||||||
|
|
|
@ -27,8 +27,10 @@ class BookmarkSharedViewPerformanceTestCase(TransactionTestCase, BookmarkFactory
|
||||||
# capture number of queries
|
# capture number of queries
|
||||||
context = CaptureQueriesContext(self.get_connection())
|
context = CaptureQueriesContext(self.get_connection())
|
||||||
with context:
|
with context:
|
||||||
response = self.client.get(reverse('bookmarks:shared'))
|
response = self.client.get(reverse("bookmarks:shared"))
|
||||||
self.assertContains(response, '<li ld-bookmark-item class="shared">', num_initial_bookmarks)
|
self.assertContains(
|
||||||
|
response, '<li ld-bookmark-item class="shared">', num_initial_bookmarks
|
||||||
|
)
|
||||||
|
|
||||||
number_of_queries = context.final_queries
|
number_of_queries = context.final_queries
|
||||||
|
|
||||||
|
@ -40,5 +42,9 @@ class BookmarkSharedViewPerformanceTestCase(TransactionTestCase, BookmarkFactory
|
||||||
|
|
||||||
# assert num queries doesn't increase
|
# assert num queries doesn't increase
|
||||||
with self.assertNumQueries(number_of_queries):
|
with self.assertNumQueries(number_of_queries):
|
||||||
response = self.client.get(reverse('bookmarks:shared'))
|
response = self.client.get(reverse("bookmarks:shared"))
|
||||||
self.assertContains(response, '<li ld-bookmark-item class="shared">', num_initial_bookmarks + num_additional_bookmarks)
|
self.assertContains(
|
||||||
|
response,
|
||||||
|
'<li ld-bookmark-item class="shared">',
|
||||||
|
num_initial_bookmarks + num_additional_bookmarks,
|
||||||
|
)
|
||||||
|
|
|
@ -9,36 +9,38 @@ from bookmarks.models import BookmarkForm, Bookmark
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
|
||||||
ENABLED_URL_VALIDATION_TEST_CASES = [
|
ENABLED_URL_VALIDATION_TEST_CASES = [
|
||||||
('thisisnotavalidurl', False),
|
("thisisnotavalidurl", False),
|
||||||
('http://domain', False),
|
("http://domain", False),
|
||||||
('unknownscheme://domain.com', False),
|
("unknownscheme://domain.com", False),
|
||||||
('http://domain.com', True),
|
("http://domain.com", True),
|
||||||
('http://www.domain.com', True),
|
("http://www.domain.com", True),
|
||||||
('https://domain.com', True),
|
("https://domain.com", True),
|
||||||
('https://www.domain.com', True),
|
("https://www.domain.com", True),
|
||||||
]
|
]
|
||||||
|
|
||||||
DISABLED_URL_VALIDATION_TEST_CASES = [
|
DISABLED_URL_VALIDATION_TEST_CASES = [
|
||||||
('thisisnotavalidurl', True),
|
("thisisnotavalidurl", True),
|
||||||
('http://domain', True),
|
("http://domain", True),
|
||||||
('unknownscheme://domain.com', True),
|
("unknownscheme://domain.com", True),
|
||||||
('http://domain.com', True),
|
("http://domain.com", True),
|
||||||
('http://www.domain.com', True),
|
("http://www.domain.com", True),
|
||||||
('https://domain.com', True),
|
("https://domain.com", True),
|
||||||
('https://www.domain.com', True),
|
("https://www.domain.com", True),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class BookmarkValidationTestCase(TestCase):
|
class BookmarkValidationTestCase(TestCase):
|
||||||
|
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
self.user = User.objects.create_user('testuser', 'test@example.com', 'password123')
|
self.user = User.objects.create_user(
|
||||||
|
"testuser", "test@example.com", "password123"
|
||||||
|
)
|
||||||
|
|
||||||
def test_bookmark_model_should_not_allow_missing_url(self):
|
def test_bookmark_model_should_not_allow_missing_url(self):
|
||||||
bookmark = Bookmark(
|
bookmark = Bookmark(
|
||||||
date_added=datetime.datetime.now(),
|
date_added=datetime.datetime.now(),
|
||||||
date_modified=datetime.datetime.now(),
|
date_modified=datetime.datetime.now(),
|
||||||
owner=self.user
|
owner=self.user,
|
||||||
)
|
)
|
||||||
|
|
||||||
with self.assertRaises(ValidationError):
|
with self.assertRaises(ValidationError):
|
||||||
|
@ -46,10 +48,10 @@ class BookmarkValidationTestCase(TestCase):
|
||||||
|
|
||||||
def test_bookmark_model_should_not_allow_empty_url(self):
|
def test_bookmark_model_should_not_allow_empty_url(self):
|
||||||
bookmark = Bookmark(
|
bookmark = Bookmark(
|
||||||
url='',
|
url="",
|
||||||
date_added=datetime.datetime.now(),
|
date_added=datetime.datetime.now(),
|
||||||
date_modified=datetime.datetime.now(),
|
date_modified=datetime.datetime.now(),
|
||||||
owner=self.user
|
owner=self.user,
|
||||||
)
|
)
|
||||||
|
|
||||||
with self.assertRaises(ValidationError):
|
with self.assertRaises(ValidationError):
|
||||||
|
@ -64,15 +66,15 @@ class BookmarkValidationTestCase(TestCase):
|
||||||
self._run_bookmark_model_url_validity_checks(DISABLED_URL_VALIDATION_TEST_CASES)
|
self._run_bookmark_model_url_validity_checks(DISABLED_URL_VALIDATION_TEST_CASES)
|
||||||
|
|
||||||
def test_bookmark_form_should_validate_required_fields(self):
|
def test_bookmark_form_should_validate_required_fields(self):
|
||||||
form = BookmarkForm(data={'url': ''})
|
form = BookmarkForm(data={"url": ""})
|
||||||
|
|
||||||
self.assertEqual(len(form.errors), 1)
|
self.assertEqual(len(form.errors), 1)
|
||||||
self.assertIn('required', str(form.errors))
|
self.assertIn("required", str(form.errors))
|
||||||
|
|
||||||
form = BookmarkForm(data={'url': None})
|
form = BookmarkForm(data={"url": None})
|
||||||
|
|
||||||
self.assertEqual(len(form.errors), 1)
|
self.assertEqual(len(form.errors), 1)
|
||||||
self.assertIn('required', str(form.errors))
|
self.assertIn("required", str(form.errors))
|
||||||
|
|
||||||
@override_settings(LD_DISABLE_URL_VALIDATION=False)
|
@override_settings(LD_DISABLE_URL_VALIDATION=False)
|
||||||
def test_bookmark_form_should_validate_url_if_not_disabled_in_settings(self):
|
def test_bookmark_form_should_validate_url_if_not_disabled_in_settings(self):
|
||||||
|
@ -89,23 +91,25 @@ class BookmarkValidationTestCase(TestCase):
|
||||||
url=url,
|
url=url,
|
||||||
date_added=datetime.datetime.now(),
|
date_added=datetime.datetime.now(),
|
||||||
date_modified=datetime.datetime.now(),
|
date_modified=datetime.datetime.now(),
|
||||||
owner=self.user
|
owner=self.user,
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
bookmark.full_clean()
|
bookmark.full_clean()
|
||||||
self.assertTrue(expectation, 'Did not expect validation error')
|
self.assertTrue(expectation, "Did not expect validation error")
|
||||||
except ValidationError as e:
|
except ValidationError as e:
|
||||||
self.assertFalse(expectation, 'Expected validation error')
|
self.assertFalse(expectation, "Expected validation error")
|
||||||
self.assertTrue('url' in e.message_dict, 'Expected URL validation to fail')
|
self.assertTrue(
|
||||||
|
"url" in e.message_dict, "Expected URL validation to fail"
|
||||||
|
)
|
||||||
|
|
||||||
def _run_bookmark_form_url_validity_checks(self, cases):
|
def _run_bookmark_form_url_validity_checks(self, cases):
|
||||||
for case in cases:
|
for case in cases:
|
||||||
url, expectation = case
|
url, expectation = case
|
||||||
form = BookmarkForm(data={'url': url})
|
form = BookmarkForm(data={"url": url})
|
||||||
|
|
||||||
if expectation:
|
if expectation:
|
||||||
self.assertEqual(len(form.errors), 0)
|
self.assertEqual(len(form.errors), 0)
|
||||||
else:
|
else:
|
||||||
self.assertEqual(len(form.errors), 1)
|
self.assertEqual(len(form.errors), 1)
|
||||||
self.assertIn('Enter a valid URL', str(form.errors))
|
self.assertIn("Enter a valid URL", str(form.errors))
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -11,8 +11,10 @@ from bookmarks.tests.helpers import LinkdingApiTestCase, BookmarkFactoryMixin
|
||||||
class BookmarksApiPerformanceTestCase(LinkdingApiTestCase, BookmarkFactoryMixin):
|
class BookmarksApiPerformanceTestCase(LinkdingApiTestCase, BookmarkFactoryMixin):
|
||||||
|
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
self.api_token = Token.objects.get_or_create(user=self.get_or_create_test_user())[0]
|
self.api_token = Token.objects.get_or_create(
|
||||||
self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.api_token.key)
|
user=self.get_or_create_test_user()
|
||||||
|
)[0]
|
||||||
|
self.client.credentials(HTTP_AUTHORIZATION="Token " + self.api_token.key)
|
||||||
|
|
||||||
def get_connection(self):
|
def get_connection(self):
|
||||||
return connections[DEFAULT_DB_ALIAS]
|
return connections[DEFAULT_DB_ALIAS]
|
||||||
|
@ -26,7 +28,10 @@ class BookmarksApiPerformanceTestCase(LinkdingApiTestCase, BookmarkFactoryMixin)
|
||||||
# capture number of queries
|
# capture number of queries
|
||||||
context = CaptureQueriesContext(self.get_connection())
|
context = CaptureQueriesContext(self.get_connection())
|
||||||
with context:
|
with context:
|
||||||
self.get(reverse('bookmarks:bookmark-list'), expected_status_code=status.HTTP_200_OK)
|
self.get(
|
||||||
|
reverse("bookmarks:bookmark-list"),
|
||||||
|
expected_status_code=status.HTTP_200_OK,
|
||||||
|
)
|
||||||
|
|
||||||
number_of_queries = context.final_queries
|
number_of_queries = context.final_queries
|
||||||
|
|
||||||
|
@ -41,7 +46,10 @@ class BookmarksApiPerformanceTestCase(LinkdingApiTestCase, BookmarkFactoryMixin)
|
||||||
# capture number of queries
|
# capture number of queries
|
||||||
context = CaptureQueriesContext(self.get_connection())
|
context = CaptureQueriesContext(self.get_connection())
|
||||||
with context:
|
with context:
|
||||||
self.get(reverse('bookmarks:bookmark-archived'), expected_status_code=status.HTTP_200_OK)
|
self.get(
|
||||||
|
reverse("bookmarks:bookmark-archived"),
|
||||||
|
expected_status_code=status.HTTP_200_OK,
|
||||||
|
)
|
||||||
|
|
||||||
number_of_queries = context.final_queries
|
number_of_queries = context.final_queries
|
||||||
|
|
||||||
|
@ -57,7 +65,10 @@ class BookmarksApiPerformanceTestCase(LinkdingApiTestCase, BookmarkFactoryMixin)
|
||||||
# capture number of queries
|
# capture number of queries
|
||||||
context = CaptureQueriesContext(self.get_connection())
|
context = CaptureQueriesContext(self.get_connection())
|
||||||
with context:
|
with context:
|
||||||
self.get(reverse('bookmarks:bookmark-shared'), expected_status_code=status.HTTP_200_OK)
|
self.get(
|
||||||
|
reverse("bookmarks:bookmark-shared"),
|
||||||
|
expected_status_code=status.HTTP_200_OK,
|
||||||
|
)
|
||||||
|
|
||||||
number_of_queries = context.final_queries
|
number_of_queries = context.final_queries
|
||||||
|
|
||||||
|
|
|
@ -9,47 +9,68 @@ from bookmarks.tests.helpers import LinkdingApiTestCase, BookmarkFactoryMixin
|
||||||
|
|
||||||
class BookmarksApiPermissionsTestCase(LinkdingApiTestCase, BookmarkFactoryMixin):
|
class BookmarksApiPermissionsTestCase(LinkdingApiTestCase, BookmarkFactoryMixin):
|
||||||
def authenticate(self) -> None:
|
def authenticate(self) -> None:
|
||||||
self.api_token = Token.objects.get_or_create(user=self.get_or_create_test_user())[0]
|
self.api_token = Token.objects.get_or_create(
|
||||||
self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.api_token.key)
|
user=self.get_or_create_test_user()
|
||||||
|
)[0]
|
||||||
|
self.client.credentials(HTTP_AUTHORIZATION="Token " + self.api_token.key)
|
||||||
|
|
||||||
def test_list_bookmarks_requires_authentication(self):
|
def test_list_bookmarks_requires_authentication(self):
|
||||||
self.get(reverse('bookmarks:bookmark-list'), expected_status_code=status.HTTP_401_UNAUTHORIZED)
|
self.get(
|
||||||
|
reverse("bookmarks:bookmark-list"),
|
||||||
|
expected_status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
)
|
||||||
|
|
||||||
self.authenticate()
|
self.authenticate()
|
||||||
self.get(reverse('bookmarks:bookmark-list'), expected_status_code=status.HTTP_200_OK)
|
self.get(
|
||||||
|
reverse("bookmarks:bookmark-list"), expected_status_code=status.HTTP_200_OK
|
||||||
|
)
|
||||||
|
|
||||||
def test_list_archived_bookmarks_requires_authentication(self):
|
def test_list_archived_bookmarks_requires_authentication(self):
|
||||||
self.get(reverse('bookmarks:bookmark-archived'), expected_status_code=status.HTTP_401_UNAUTHORIZED)
|
self.get(
|
||||||
|
reverse("bookmarks:bookmark-archived"),
|
||||||
|
expected_status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
)
|
||||||
|
|
||||||
self.authenticate()
|
self.authenticate()
|
||||||
self.get(reverse('bookmarks:bookmark-archived'), expected_status_code=status.HTTP_200_OK)
|
self.get(
|
||||||
|
reverse("bookmarks:bookmark-archived"),
|
||||||
|
expected_status_code=status.HTTP_200_OK,
|
||||||
|
)
|
||||||
|
|
||||||
def test_list_shared_bookmarks_does_not_require_authentication(self):
|
def test_list_shared_bookmarks_does_not_require_authentication(self):
|
||||||
self.get(reverse('bookmarks:bookmark-shared'), expected_status_code=status.HTTP_200_OK)
|
self.get(
|
||||||
|
reverse("bookmarks:bookmark-shared"),
|
||||||
|
expected_status_code=status.HTTP_200_OK,
|
||||||
|
)
|
||||||
|
|
||||||
self.authenticate()
|
self.authenticate()
|
||||||
self.get(reverse('bookmarks:bookmark-shared'), expected_status_code=status.HTTP_200_OK)
|
self.get(
|
||||||
|
reverse("bookmarks:bookmark-shared"),
|
||||||
|
expected_status_code=status.HTTP_200_OK,
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_bookmark_requires_authentication(self):
|
def test_create_bookmark_requires_authentication(self):
|
||||||
data = {
|
data = {
|
||||||
'url': 'https://example.com/',
|
"url": "https://example.com/",
|
||||||
'title': 'Test title',
|
"title": "Test title",
|
||||||
'description': 'Test description',
|
"description": "Test description",
|
||||||
'notes': 'Test notes',
|
"notes": "Test notes",
|
||||||
'is_archived': False,
|
"is_archived": False,
|
||||||
'unread': False,
|
"unread": False,
|
||||||
'shared': False,
|
"shared": False,
|
||||||
'tag_names': ['tag1', 'tag2']
|
"tag_names": ["tag1", "tag2"],
|
||||||
}
|
}
|
||||||
|
|
||||||
self.post(reverse('bookmarks:bookmark-list'), data, status.HTTP_401_UNAUTHORIZED)
|
self.post(
|
||||||
|
reverse("bookmarks:bookmark-list"), data, status.HTTP_401_UNAUTHORIZED
|
||||||
|
)
|
||||||
|
|
||||||
self.authenticate()
|
self.authenticate()
|
||||||
self.post(reverse('bookmarks:bookmark-list'), data, status.HTTP_201_CREATED)
|
self.post(reverse("bookmarks:bookmark-list"), data, status.HTTP_201_CREATED)
|
||||||
|
|
||||||
def test_get_bookmark_requires_authentication(self):
|
def test_get_bookmark_requires_authentication(self):
|
||||||
bookmark = self.setup_bookmark()
|
bookmark = self.setup_bookmark()
|
||||||
url = reverse('bookmarks:bookmark-detail', args=[bookmark.id])
|
url = reverse("bookmarks:bookmark-detail", args=[bookmark.id])
|
||||||
|
|
||||||
self.get(url, expected_status_code=status.HTTP_401_UNAUTHORIZED)
|
self.get(url, expected_status_code=status.HTTP_401_UNAUTHORIZED)
|
||||||
|
|
||||||
|
@ -58,8 +79,8 @@ class BookmarksApiPermissionsTestCase(LinkdingApiTestCase, BookmarkFactoryMixin)
|
||||||
|
|
||||||
def test_update_bookmark_requires_authentication(self):
|
def test_update_bookmark_requires_authentication(self):
|
||||||
bookmark = self.setup_bookmark()
|
bookmark = self.setup_bookmark()
|
||||||
data = {'url': 'https://example.com/'}
|
data = {"url": "https://example.com/"}
|
||||||
url = reverse('bookmarks:bookmark-detail', args=[bookmark.id])
|
url = reverse("bookmarks:bookmark-detail", args=[bookmark.id])
|
||||||
|
|
||||||
self.put(url, data, expected_status_code=status.HTTP_401_UNAUTHORIZED)
|
self.put(url, data, expected_status_code=status.HTTP_401_UNAUTHORIZED)
|
||||||
|
|
||||||
|
@ -68,8 +89,8 @@ class BookmarksApiPermissionsTestCase(LinkdingApiTestCase, BookmarkFactoryMixin)
|
||||||
|
|
||||||
def test_patch_bookmark_requires_authentication(self):
|
def test_patch_bookmark_requires_authentication(self):
|
||||||
bookmark = self.setup_bookmark()
|
bookmark = self.setup_bookmark()
|
||||||
data = {'url': 'https://example.com'}
|
data = {"url": "https://example.com"}
|
||||||
url = reverse('bookmarks:bookmark-detail', args=[bookmark.id])
|
url = reverse("bookmarks:bookmark-detail", args=[bookmark.id])
|
||||||
|
|
||||||
self.patch(url, data, expected_status_code=status.HTTP_401_UNAUTHORIZED)
|
self.patch(url, data, expected_status_code=status.HTTP_401_UNAUTHORIZED)
|
||||||
|
|
||||||
|
@ -78,7 +99,7 @@ class BookmarksApiPermissionsTestCase(LinkdingApiTestCase, BookmarkFactoryMixin)
|
||||||
|
|
||||||
def test_delete_bookmark_requires_authentication(self):
|
def test_delete_bookmark_requires_authentication(self):
|
||||||
bookmark = self.setup_bookmark()
|
bookmark = self.setup_bookmark()
|
||||||
url = reverse('bookmarks:bookmark-detail', args=[bookmark.id])
|
url = reverse("bookmarks:bookmark-detail", args=[bookmark.id])
|
||||||
|
|
||||||
self.delete(url, expected_status_code=status.HTTP_401_UNAUTHORIZED)
|
self.delete(url, expected_status_code=status.HTTP_401_UNAUTHORIZED)
|
||||||
|
|
||||||
|
@ -87,7 +108,7 @@ class BookmarksApiPermissionsTestCase(LinkdingApiTestCase, BookmarkFactoryMixin)
|
||||||
|
|
||||||
def test_archive_requires_authentication(self):
|
def test_archive_requires_authentication(self):
|
||||||
bookmark = self.setup_bookmark()
|
bookmark = self.setup_bookmark()
|
||||||
url = reverse('bookmarks:bookmark-archive', args=[bookmark.id])
|
url = reverse("bookmarks:bookmark-archive", args=[bookmark.id])
|
||||||
|
|
||||||
self.post(url, expected_status_code=status.HTTP_401_UNAUTHORIZED)
|
self.post(url, expected_status_code=status.HTTP_401_UNAUTHORIZED)
|
||||||
|
|
||||||
|
@ -96,7 +117,7 @@ class BookmarksApiPermissionsTestCase(LinkdingApiTestCase, BookmarkFactoryMixin)
|
||||||
|
|
||||||
def test_unarchive_requires_authentication(self):
|
def test_unarchive_requires_authentication(self):
|
||||||
bookmark = self.setup_bookmark(is_archived=True)
|
bookmark = self.setup_bookmark(is_archived=True)
|
||||||
url = reverse('bookmarks:bookmark-unarchive', args=[bookmark.id])
|
url = reverse("bookmarks:bookmark-unarchive", args=[bookmark.id])
|
||||||
|
|
||||||
self.post(url, expected_status_code=status.HTTP_401_UNAUTHORIZED)
|
self.post(url, expected_status_code=status.HTTP_401_UNAUTHORIZED)
|
||||||
|
|
||||||
|
@ -104,16 +125,18 @@ class BookmarksApiPermissionsTestCase(LinkdingApiTestCase, BookmarkFactoryMixin)
|
||||||
self.post(url, expected_status_code=status.HTTP_204_NO_CONTENT)
|
self.post(url, expected_status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
def test_check_requires_authentication(self):
|
def test_check_requires_authentication(self):
|
||||||
url = reverse('bookmarks:bookmark-check')
|
url = reverse("bookmarks:bookmark-check")
|
||||||
check_url = urllib.parse.quote_plus('https://example.com')
|
check_url = urllib.parse.quote_plus("https://example.com")
|
||||||
|
|
||||||
self.get(f'{url}?url={check_url}', expected_status_code=status.HTTP_401_UNAUTHORIZED)
|
self.get(
|
||||||
|
f"{url}?url={check_url}", expected_status_code=status.HTTP_401_UNAUTHORIZED
|
||||||
|
)
|
||||||
|
|
||||||
self.authenticate()
|
self.authenticate()
|
||||||
self.get(f'{url}?url={check_url}', expected_status_code=status.HTTP_200_OK)
|
self.get(f"{url}?url={check_url}", expected_status_code=status.HTTP_200_OK)
|
||||||
|
|
||||||
def test_user_profile_requires_authentication(self):
|
def test_user_profile_requires_authentication(self):
|
||||||
url = reverse('bookmarks:user-profile')
|
url = reverse("bookmarks:user-profile")
|
||||||
|
|
||||||
self.get(url, expected_status_code=status.HTTP_401_UNAUTHORIZED)
|
self.get(url, expected_status_code=status.HTTP_401_UNAUTHORIZED)
|
||||||
|
|
||||||
|
|
|
@ -16,34 +16,48 @@ from bookmarks.views.partials import contexts
|
||||||
|
|
||||||
class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin):
|
class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin):
|
||||||
|
|
||||||
def assertBookmarksLink(self, html: str, bookmark: Bookmark, link_target: str = '_blank'):
|
def assertBookmarksLink(
|
||||||
favicon_img = f'<img src="/static/{bookmark.favicon_file}" alt="">' if bookmark.favicon_file else ''
|
self, html: str, bookmark: Bookmark, link_target: str = "_blank"
|
||||||
|
):
|
||||||
|
favicon_img = (
|
||||||
|
f'<img src="/static/{bookmark.favicon_file}" alt="">'
|
||||||
|
if bookmark.favicon_file
|
||||||
|
else ""
|
||||||
|
)
|
||||||
self.assertInHTML(
|
self.assertInHTML(
|
||||||
f'''
|
f"""
|
||||||
<a href="{bookmark.url}"
|
<a href="{bookmark.url}"
|
||||||
target="{link_target}"
|
target="{link_target}"
|
||||||
rel="noopener">
|
rel="noopener">
|
||||||
{favicon_img}
|
{favicon_img}
|
||||||
<span>{bookmark.resolved_title}</span>
|
<span>{bookmark.resolved_title}</span>
|
||||||
</a>
|
</a>
|
||||||
''',
|
""",
|
||||||
html
|
html,
|
||||||
)
|
)
|
||||||
|
|
||||||
def assertDateLabel(self, html: str, label_content: str):
|
def assertDateLabel(self, html: str, label_content: str):
|
||||||
self.assertInHTML(f'''
|
self.assertInHTML(
|
||||||
|
f"""
|
||||||
<span>{label_content}</span>
|
<span>{label_content}</span>
|
||||||
<span class="separator">|</span>
|
<span class="separator">|</span>
|
||||||
''', html)
|
""",
|
||||||
|
html,
|
||||||
|
)
|
||||||
|
|
||||||
def assertWebArchiveLink(self, html: str, label_content: str, url: str, link_target: str = '_blank'):
|
def assertWebArchiveLink(
|
||||||
self.assertInHTML(f'''
|
self, html: str, label_content: str, url: str, link_target: str = "_blank"
|
||||||
|
):
|
||||||
|
self.assertInHTML(
|
||||||
|
f"""
|
||||||
<a href="{url}"
|
<a href="{url}"
|
||||||
title="Show snapshot on the Internet Archive Wayback Machine" target="{link_target}" rel="noopener">
|
title="Show snapshot on the Internet Archive Wayback Machine" target="{link_target}" rel="noopener">
|
||||||
{label_content} ∞
|
{label_content} ∞
|
||||||
</a>
|
</a>
|
||||||
<span class="separator">|</span>
|
<span class="separator">|</span>
|
||||||
''', html)
|
""",
|
||||||
|
html,
|
||||||
|
)
|
||||||
|
|
||||||
def assertBookmarkActions(self, html: str, bookmark: Bookmark):
|
def assertBookmarkActions(self, html: str, bookmark: Bookmark):
|
||||||
self.assertBookmarkActionsCount(html, bookmark, count=1)
|
self.assertBookmarkActionsCount(html, bookmark, count=1)
|
||||||
|
@ -53,20 +67,32 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin):
|
||||||
|
|
||||||
def assertBookmarkActionsCount(self, html: str, bookmark: Bookmark, count=1):
|
def assertBookmarkActionsCount(self, html: str, bookmark: Bookmark, count=1):
|
||||||
# Edit link
|
# Edit link
|
||||||
edit_url = reverse('bookmarks:edit', args=[bookmark.id])
|
edit_url = reverse("bookmarks:edit", args=[bookmark.id])
|
||||||
self.assertInHTML(f'''
|
self.assertInHTML(
|
||||||
|
f"""
|
||||||
<a href="{edit_url}?return_url=/bookmarks">Edit</a>
|
<a href="{edit_url}?return_url=/bookmarks">Edit</a>
|
||||||
''', html, count=count)
|
""",
|
||||||
|
html,
|
||||||
|
count=count,
|
||||||
|
)
|
||||||
# Archive link
|
# Archive link
|
||||||
self.assertInHTML(f'''
|
self.assertInHTML(
|
||||||
|
f"""
|
||||||
<button type="submit" name="archive" value="{bookmark.id}"
|
<button type="submit" name="archive" value="{bookmark.id}"
|
||||||
class="btn btn-link btn-sm">Archive</button>
|
class="btn btn-link btn-sm">Archive</button>
|
||||||
''', html, count=count)
|
""",
|
||||||
|
html,
|
||||||
|
count=count,
|
||||||
|
)
|
||||||
# Delete link
|
# Delete link
|
||||||
self.assertInHTML(f'''
|
self.assertInHTML(
|
||||||
|
f"""
|
||||||
<button ld-confirm-button type="submit" name="remove" value="{bookmark.id}"
|
<button ld-confirm-button type="submit" name="remove" value="{bookmark.id}"
|
||||||
class="btn btn-link btn-sm">Remove</button>
|
class="btn btn-link btn-sm">Remove</button>
|
||||||
''', html, count=count)
|
""",
|
||||||
|
html,
|
||||||
|
count=count,
|
||||||
|
)
|
||||||
|
|
||||||
def assertShareInfo(self, html: str, bookmark: Bookmark):
|
def assertShareInfo(self, html: str, bookmark: Bookmark):
|
||||||
self.assertShareInfoCount(html, bookmark, 1)
|
self.assertShareInfoCount(html, bookmark, 1)
|
||||||
|
@ -75,11 +101,15 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin):
|
||||||
self.assertShareInfoCount(html, bookmark, 0)
|
self.assertShareInfoCount(html, bookmark, 0)
|
||||||
|
|
||||||
def assertShareInfoCount(self, html: str, bookmark: Bookmark, count=1):
|
def assertShareInfoCount(self, html: str, bookmark: Bookmark, count=1):
|
||||||
self.assertInHTML(f'''
|
self.assertInHTML(
|
||||||
|
f"""
|
||||||
<span>Shared by
|
<span>Shared by
|
||||||
<a href="?user={bookmark.owner.username}">{bookmark.owner.username}</a>
|
<a href="?user={bookmark.owner.username}">{bookmark.owner.username}</a>
|
||||||
</span>
|
</span>
|
||||||
''', html, count=count)
|
""",
|
||||||
|
html,
|
||||||
|
count=count,
|
||||||
|
)
|
||||||
|
|
||||||
def assertFaviconVisible(self, html: str, bookmark: Bookmark):
|
def assertFaviconVisible(self, html: str, bookmark: Bookmark):
|
||||||
self.assertFaviconCount(html, bookmark, 1)
|
self.assertFaviconCount(html, bookmark, 1)
|
||||||
|
@ -88,47 +118,68 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin):
|
||||||
self.assertFaviconCount(html, bookmark, 0)
|
self.assertFaviconCount(html, bookmark, 0)
|
||||||
|
|
||||||
def assertFaviconCount(self, html: str, bookmark: Bookmark, count=1):
|
def assertFaviconCount(self, html: str, bookmark: Bookmark, count=1):
|
||||||
self.assertInHTML(f'''
|
self.assertInHTML(
|
||||||
|
f"""
|
||||||
<img src="/static/{bookmark.favicon_file}" alt="">
|
<img src="/static/{bookmark.favicon_file}" alt="">
|
||||||
''', html, count=count)
|
""",
|
||||||
|
html,
|
||||||
|
count=count,
|
||||||
|
)
|
||||||
|
|
||||||
def assertBookmarkURLCount(self, html: str, bookmark: Bookmark, link_target: str = '_blank', count=0):
|
def assertBookmarkURLCount(
|
||||||
self.assertInHTML(f'''
|
self, html: str, bookmark: Bookmark, link_target: str = "_blank", count=0
|
||||||
|
):
|
||||||
|
self.assertInHTML(
|
||||||
|
f"""
|
||||||
<div class="url-path truncate">
|
<div class="url-path truncate">
|
||||||
<a href="{bookmark.url}" target="{link_target}" rel="noopener"
|
<a href="{bookmark.url}" target="{link_target}" rel="noopener"
|
||||||
class="url-display text-sm">
|
class="url-display text-sm">
|
||||||
{bookmark.url}
|
{bookmark.url}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
''', html, count)
|
""",
|
||||||
|
html,
|
||||||
|
count,
|
||||||
|
)
|
||||||
|
|
||||||
def assertBookmarkURLVisible(self, html: str, bookmark: Bookmark):
|
def assertBookmarkURLVisible(self, html: str, bookmark: Bookmark):
|
||||||
self.assertBookmarkURLCount(html, bookmark, count=1)
|
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)
|
self.assertBookmarkURLCount(html, bookmark, count=0)
|
||||||
|
|
||||||
def assertNotes(self, html: str, notes_html: str, count=1):
|
def assertNotes(self, html: str, notes_html: str, count=1):
|
||||||
self.assertInHTML(f'''
|
self.assertInHTML(
|
||||||
|
f"""
|
||||||
<div class="notes bg-gray text-gray-dark">
|
<div class="notes bg-gray text-gray-dark">
|
||||||
<div class="notes-content">
|
<div class="notes-content">
|
||||||
{notes_html}
|
{notes_html}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
''', html, count=count)
|
""",
|
||||||
|
html,
|
||||||
|
count=count,
|
||||||
|
)
|
||||||
|
|
||||||
def assertNotesToggle(self, html: str, count=1):
|
def assertNotesToggle(self, html: str, count=1):
|
||||||
self.assertInHTML(f'''
|
self.assertInHTML(
|
||||||
|
f"""
|
||||||
<button type="button" class="btn btn-link btn-sm btn-icon toggle-notes">
|
<button type="button" class="btn btn-link btn-sm btn-icon toggle-notes">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16">
|
||||||
<use xlink:href="#ld-icon-note"></use>
|
<use xlink:href="#ld-icon-note"></use>
|
||||||
</svg>
|
</svg>
|
||||||
Notes
|
Notes
|
||||||
</button>
|
</button>
|
||||||
''', html, count=count)
|
""",
|
||||||
|
html,
|
||||||
|
count=count,
|
||||||
|
)
|
||||||
|
|
||||||
def assertUnshareButton(self, html: str, bookmark: Bookmark, count=1):
|
def assertUnshareButton(self, html: str, bookmark: Bookmark, count=1):
|
||||||
self.assertInHTML(f'''
|
self.assertInHTML(
|
||||||
|
f"""
|
||||||
<button type="submit" name="unshare" value="{bookmark.id}"
|
<button type="submit" name="unshare" value="{bookmark.id}"
|
||||||
class="btn btn-link btn-sm btn-icon"
|
class="btn btn-link btn-sm btn-icon"
|
||||||
ld-confirm-button confirm-icon="ld-icon-unshare" confirm-question="Unshare?">
|
ld-confirm-button confirm-icon="ld-icon-unshare" confirm-question="Unshare?">
|
||||||
|
@ -137,10 +188,14 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin):
|
||||||
</svg>
|
</svg>
|
||||||
Shared
|
Shared
|
||||||
</button>
|
</button>
|
||||||
''', html, count=count)
|
""",
|
||||||
|
html,
|
||||||
|
count=count,
|
||||||
|
)
|
||||||
|
|
||||||
def assertMarkAsReadButton(self, html: str, bookmark: Bookmark, count=1):
|
def assertMarkAsReadButton(self, html: str, bookmark: Bookmark, count=1):
|
||||||
self.assertInHTML(f'''
|
self.assertInHTML(
|
||||||
|
f"""
|
||||||
<button type="submit" name="mark_as_read" value="{bookmark.id}"
|
<button type="submit" name="mark_as_read" value="{bookmark.id}"
|
||||||
class="btn btn-link btn-sm btn-icon"
|
class="btn btn-link btn-sm btn-icon"
|
||||||
ld-confirm-button confirm-icon="ld-icon-read" confirm-question="Mark as read?">
|
ld-confirm-button confirm-icon="ld-icon-read" confirm-question="Mark as read?">
|
||||||
|
@ -149,12 +204,19 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin):
|
||||||
</svg>
|
</svg>
|
||||||
Unread
|
Unread
|
||||||
</button>
|
</button>
|
||||||
''', html, count=count)
|
""",
|
||||||
|
html,
|
||||||
|
count=count,
|
||||||
|
)
|
||||||
|
|
||||||
def render_template(self,
|
def render_template(
|
||||||
url='/bookmarks',
|
self,
|
||||||
context_type: Type[contexts.BookmarkListContext] = contexts.ActiveBookmarkListContext,
|
url="/bookmarks",
|
||||||
user: User | AnonymousUser = None) -> str:
|
context_type: Type[
|
||||||
|
contexts.BookmarkListContext
|
||||||
|
] = contexts.ActiveBookmarkListContext,
|
||||||
|
user: User | AnonymousUser = None,
|
||||||
|
) -> str:
|
||||||
rf = RequestFactory()
|
rf = RequestFactory()
|
||||||
request = rf.get(url)
|
request = rf.get(url)
|
||||||
request.user = user or self.get_or_create_test_user()
|
request.user = user or self.get_or_create_test_user()
|
||||||
|
@ -162,14 +224,14 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin):
|
||||||
middleware(request)
|
middleware(request)
|
||||||
|
|
||||||
bookmark_list_context = context_type(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(
|
template = Template("{% include 'bookmarks/bookmark_list.html' %}")
|
||||||
"{% include 'bookmarks/bookmark_list.html' %}"
|
|
||||||
)
|
|
||||||
return template.render(context)
|
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 = self.setup_bookmark()
|
||||||
bookmark.date_added = timezone.now() - relativedelta(days=8)
|
bookmark.date_added = timezone.now() - relativedelta(days=8)
|
||||||
bookmark.web_archive_snapshot_url = web_archive_url
|
bookmark.web_archive_snapshot_url = web_archive_url
|
||||||
|
@ -180,38 +242,46 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin):
|
||||||
return bookmark
|
return bookmark
|
||||||
|
|
||||||
def test_should_respect_absolute_date_setting(self):
|
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()
|
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)
|
self.assertDateLabel(html, formatted_date)
|
||||||
|
|
||||||
def test_should_render_web_archive_link_with_absolute_date_setting(self):
|
def test_should_render_web_archive_link_with_absolute_date_setting(self):
|
||||||
bookmark = self.setup_date_format_test(UserProfile.BOOKMARK_DATE_DISPLAY_ABSOLUTE,
|
bookmark = self.setup_date_format_test(
|
||||||
'https://web.archive.org/web/20210811214511/https://wanikani.com/')
|
UserProfile.BOOKMARK_DATE_DISPLAY_ABSOLUTE,
|
||||||
|
"https://web.archive.org/web/20210811214511/https://wanikani.com/",
|
||||||
|
)
|
||||||
html = self.render_template()
|
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):
|
def test_should_respect_relative_date_setting(self):
|
||||||
self.setup_date_format_test(UserProfile.BOOKMARK_DATE_DISPLAY_RELATIVE)
|
self.setup_date_format_test(UserProfile.BOOKMARK_DATE_DISPLAY_RELATIVE)
|
||||||
html = self.render_template()
|
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):
|
def test_should_render_web_archive_link_with_relative_date_setting(self):
|
||||||
bookmark = self.setup_date_format_test(UserProfile.BOOKMARK_DATE_DISPLAY_RELATIVE,
|
bookmark = self.setup_date_format_test(
|
||||||
'https://web.archive.org/web/20210811214511/https://wanikani.com/')
|
UserProfile.BOOKMARK_DATE_DISPLAY_RELATIVE,
|
||||||
|
"https://web.archive.org/web/20210811214511/https://wanikani.com/",
|
||||||
|
)
|
||||||
html = self.render_template()
|
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):
|
def test_bookmark_link_target_should_be_blank_by_default(self):
|
||||||
bookmark = self.setup_bookmark()
|
bookmark = self.setup_bookmark()
|
||||||
html = self.render_template()
|
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):
|
def test_bookmark_link_target_should_respect_user_profile(self):
|
||||||
profile = self.get_or_create_test_user().profile
|
profile = self.get_or_create_test_user().profile
|
||||||
|
@ -221,17 +291,19 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin):
|
||||||
bookmark = self.setup_bookmark()
|
bookmark = self.setup_bookmark()
|
||||||
html = self.render_template()
|
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):
|
def test_web_archive_link_target_should_be_blank_by_default(self):
|
||||||
bookmark = self.setup_bookmark()
|
bookmark = self.setup_bookmark()
|
||||||
bookmark.date_added = timezone.now() - relativedelta(days=8)
|
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()
|
bookmark.save()
|
||||||
|
|
||||||
html = self.render_template()
|
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):
|
def test_web_archive_link_target_should_respect_user_profile(self):
|
||||||
profile = self.get_or_create_test_user().profile
|
profile = self.get_or_create_test_user().profile
|
||||||
|
@ -240,12 +312,14 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin):
|
||||||
|
|
||||||
bookmark = self.setup_bookmark()
|
bookmark = self.setup_bookmark()
|
||||||
bookmark.date_added = timezone.now() - relativedelta(days=8)
|
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()
|
bookmark.save()
|
||||||
|
|
||||||
html = self.render_template()
|
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):
|
def test_should_reflect_unread_state_as_css_class(self):
|
||||||
self.setup_bookmark(unread=True)
|
self.setup_bookmark(unread=True)
|
||||||
|
@ -281,7 +355,9 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin):
|
||||||
self.assertNoShareInfo(html, bookmark)
|
self.assertNoShareInfo(html, bookmark)
|
||||||
|
|
||||||
def test_show_share_info_for_non_owned_bookmarks(self):
|
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.enable_sharing = True
|
||||||
other_user.profile.save()
|
other_user.profile.save()
|
||||||
|
|
||||||
|
@ -292,25 +368,32 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin):
|
||||||
self.assertShareInfo(html, bookmark)
|
self.assertShareInfo(html, bookmark)
|
||||||
|
|
||||||
def test_share_info_user_link_keeps_query_params(self):
|
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.enable_sharing = True
|
||||||
other_user.profile.save()
|
other_user.profile.save()
|
||||||
|
|
||||||
bookmark = self.setup_bookmark(user=other_user, shared=True, title='foo')
|
bookmark = self.setup_bookmark(user=other_user, shared=True, title="foo")
|
||||||
html = self.render_template(url='/bookmarks?q=foo', context_type=contexts.SharedBookmarkListContext)
|
html = self.render_template(
|
||||||
|
url="/bookmarks?q=foo", context_type=contexts.SharedBookmarkListContext
|
||||||
|
)
|
||||||
|
|
||||||
self.assertInHTML(f'''
|
self.assertInHTML(
|
||||||
|
f"""
|
||||||
<span>Shared by
|
<span>Shared by
|
||||||
<a href="?q=foo&user={bookmark.owner.username}">{bookmark.owner.username}</a>
|
<a href="?q=foo&user={bookmark.owner.username}">{bookmark.owner.username}</a>
|
||||||
</span>
|
</span>
|
||||||
''', html)
|
""",
|
||||||
|
html,
|
||||||
|
)
|
||||||
|
|
||||||
def test_favicon_should_be_visible_when_favicons_enabled(self):
|
def test_favicon_should_be_visible_when_favicons_enabled(self):
|
||||||
profile = self.get_or_create_test_user().profile
|
profile = self.get_or_create_test_user().profile
|
||||||
profile.enable_favicons = True
|
profile.enable_favicons = True
|
||||||
profile.save()
|
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()
|
html = self.render_template()
|
||||||
|
|
||||||
self.assertFaviconVisible(html, bookmark)
|
self.assertFaviconVisible(html, bookmark)
|
||||||
|
@ -320,7 +403,7 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin):
|
||||||
profile.enable_favicons = True
|
profile.enable_favicons = True
|
||||||
profile.save()
|
profile.save()
|
||||||
|
|
||||||
bookmark = self.setup_bookmark(favicon_file='')
|
bookmark = self.setup_bookmark(favicon_file="")
|
||||||
html = self.render_template()
|
html = self.render_template()
|
||||||
|
|
||||||
self.assertFaviconHidden(html, bookmark)
|
self.assertFaviconHidden(html, bookmark)
|
||||||
|
@ -330,7 +413,7 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin):
|
||||||
profile.enable_favicons = False
|
profile.enable_favicons = False
|
||||||
profile.save()
|
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()
|
html = self.render_template()
|
||||||
|
|
||||||
self.assertFaviconHidden(html, bookmark)
|
self.assertFaviconHidden(html, bookmark)
|
||||||
|
@ -428,21 +511,23 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin):
|
||||||
self.setup_bookmark()
|
self.setup_bookmark()
|
||||||
html = self.render_template()
|
html = self.render_template()
|
||||||
|
|
||||||
self.assertNotes(html, '', 0)
|
self.assertNotes(html, "", 0)
|
||||||
self.assertNotesToggle(html, 0)
|
self.assertNotesToggle(html, 0)
|
||||||
|
|
||||||
def test_with_notes(self):
|
def test_with_notes(self):
|
||||||
self.setup_bookmark(notes='Test note')
|
self.setup_bookmark(notes="Test note")
|
||||||
html = self.render_template()
|
html = self.render_template()
|
||||||
|
|
||||||
note_html = '<p>Test note</p>'
|
note_html = "<p>Test note</p>"
|
||||||
self.assertNotes(html, note_html, 1)
|
self.assertNotes(html, note_html, 1)
|
||||||
|
|
||||||
def test_note_renders_markdown(self):
|
def test_note_renders_markdown(self):
|
||||||
self.setup_bookmark(notes='**Example:** `print("Hello world!")`')
|
self.setup_bookmark(notes='**Example:** `print("Hello world!")`')
|
||||||
html = self.render_template()
|
html = self.render_template()
|
||||||
|
|
||||||
note_html = '<p><strong>Example:</strong> <code>print("Hello world!")</code></p>'
|
note_html = (
|
||||||
|
'<p><strong>Example:</strong> <code>print("Hello world!")</code></p>'
|
||||||
|
)
|
||||||
self.assertNotes(html, note_html, 1)
|
self.assertNotes(html, note_html, 1)
|
||||||
|
|
||||||
def test_note_cleans_html(self):
|
def test_note_cleans_html(self):
|
||||||
|
@ -453,7 +538,7 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin):
|
||||||
self.assertNotes(html, note_html, 1)
|
self.assertNotes(html, note_html, 1)
|
||||||
|
|
||||||
def test_notes_are_hidden_initially_by_default(self):
|
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())
|
html = collapse_whitespace(self.render_template())
|
||||||
|
|
||||||
self.assertIn('<ul class="bookmark-list" data-bookmarks-total="1">', html)
|
self.assertIn('<ul class="bookmark-list" data-bookmarks-total="1">', html)
|
||||||
|
@ -463,7 +548,7 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin):
|
||||||
profile.permanent_notes = False
|
profile.permanent_notes = False
|
||||||
profile.save()
|
profile.save()
|
||||||
|
|
||||||
self.setup_bookmark(notes='Test note')
|
self.setup_bookmark(notes="Test note")
|
||||||
html = collapse_whitespace(self.render_template())
|
html = collapse_whitespace(self.render_template())
|
||||||
|
|
||||||
self.assertIn('<ul class="bookmark-list" data-bookmarks-total="1">', html)
|
self.assertIn('<ul class="bookmark-list" data-bookmarks-total="1">', html)
|
||||||
|
@ -473,13 +558,15 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin):
|
||||||
profile.permanent_notes = True
|
profile.permanent_notes = True
|
||||||
profile.save()
|
profile.save()
|
||||||
|
|
||||||
self.setup_bookmark(notes='Test note')
|
self.setup_bookmark(notes="Test note")
|
||||||
html = collapse_whitespace(self.render_template())
|
html = collapse_whitespace(self.render_template())
|
||||||
|
|
||||||
self.assertIn('<ul class="bookmark-list show-notes" data-bookmarks-total="1">', html)
|
self.assertIn(
|
||||||
|
'<ul class="bookmark-list show-notes" data-bookmarks-total="1">', html
|
||||||
|
)
|
||||||
|
|
||||||
def test_toggle_notes_is_visible_by_default(self):
|
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()
|
html = self.render_template()
|
||||||
|
|
||||||
self.assertNotesToggle(html, 1)
|
self.assertNotesToggle(html, 1)
|
||||||
|
@ -489,7 +576,7 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin):
|
||||||
profile.permanent_notes = False
|
profile.permanent_notes = False
|
||||||
profile.save()
|
profile.save()
|
||||||
|
|
||||||
self.setup_bookmark(notes='Test note')
|
self.setup_bookmark(notes="Test note")
|
||||||
html = self.render_template()
|
html = self.render_template()
|
||||||
|
|
||||||
self.assertNotesToggle(html, 1)
|
self.assertNotesToggle(html, 1)
|
||||||
|
@ -499,7 +586,7 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin):
|
||||||
profile.permanent_notes = True
|
profile.permanent_notes = True
|
||||||
profile.save()
|
profile.save()
|
||||||
|
|
||||||
self.setup_bookmark(notes='Test note')
|
self.setup_bookmark(notes="Test note")
|
||||||
html = self.render_template()
|
html = self.render_template()
|
||||||
|
|
||||||
self.assertNotesToggle(html, 0)
|
self.assertNotesToggle(html, 0)
|
||||||
|
@ -512,25 +599,35 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin):
|
||||||
|
|
||||||
bookmark = self.setup_bookmark()
|
bookmark = self.setup_bookmark()
|
||||||
bookmark.date_added = timezone.now() - relativedelta(days=8)
|
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.notes = '**Example:** `print("Hello world!")`'
|
||||||
bookmark.favicon_file = 'https_example_com.png'
|
bookmark.favicon_file = "https_example_com.png"
|
||||||
bookmark.shared = True
|
bookmark.shared = True
|
||||||
bookmark.unread = True
|
bookmark.unread = True
|
||||||
bookmark.save()
|
bookmark.save()
|
||||||
|
|
||||||
html = self.render_template(context_type=contexts.SharedBookmarkListContext, user=AnonymousUser())
|
html = self.render_template(
|
||||||
self.assertBookmarksLink(html, bookmark, link_target='_blank')
|
context_type=contexts.SharedBookmarkListContext, user=AnonymousUser()
|
||||||
self.assertWebArchiveLink(html, '1 week ago', bookmark.web_archive_snapshot_url, link_target='_blank')
|
)
|
||||||
|
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.assertNoBookmarkActions(html, bookmark)
|
||||||
self.assertShareInfo(html, bookmark)
|
self.assertShareInfo(html, bookmark)
|
||||||
self.assertMarkAsReadButton(html, bookmark, count=0)
|
self.assertMarkAsReadButton(html, bookmark, count=0)
|
||||||
self.assertUnshareButton(html, bookmark, count=0)
|
self.assertUnshareButton(html, bookmark, count=0)
|
||||||
note_html = '<p><strong>Example:</strong> <code>print("Hello world!")</code></p>'
|
note_html = (
|
||||||
|
'<p><strong>Example:</strong> <code>print("Hello world!")</code></p>'
|
||||||
|
)
|
||||||
self.assertNotes(html, note_html, 1)
|
self.assertNotes(html, note_html, 1)
|
||||||
self.assertFaviconVisible(html, bookmark)
|
self.assertFaviconVisible(html, bookmark)
|
||||||
|
|
||||||
def test_empty_state(self):
|
def test_empty_state(self):
|
||||||
html = self.render_template()
|
html = self.render_template()
|
||||||
|
|
||||||
self.assertInHTML('<p class="empty-title h5">You have no bookmarks yet</p>', html)
|
self.assertInHTML(
|
||||||
|
'<p class="empty-title h5">You have no bookmarks yet</p>', html
|
||||||
|
)
|
||||||
|
|
|
@ -6,11 +6,17 @@ from bookmarks.models import Bookmark
|
||||||
class BookmarkTestCase(TestCase):
|
class BookmarkTestCase(TestCase):
|
||||||
|
|
||||||
def test_bookmark_resolved_title(self):
|
def test_bookmark_resolved_title(self):
|
||||||
bookmark = Bookmark(title='Custom title', website_title='Website title', url='https://example.com')
|
bookmark = Bookmark(
|
||||||
self.assertEqual(bookmark.resolved_title, 'Custom title')
|
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')
|
bookmark = Bookmark(
|
||||||
self.assertEqual(bookmark.resolved_title, 'Website title')
|
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')
|
bookmark = Bookmark(title="", website_title="", url="https://example.com")
|
||||||
self.assertEqual(bookmark.resolved_title, 'https://example.com')
|
self.assertEqual(bookmark.resolved_title, "https://example.com")
|
||||||
|
|
|
@ -7,9 +7,21 @@ from django.utils import timezone
|
||||||
from bookmarks.models import Bookmark, Tag
|
from bookmarks.models import Bookmark, Tag
|
||||||
from bookmarks.services import tasks
|
from bookmarks.services import tasks
|
||||||
from bookmarks.services import website_loader
|
from bookmarks.services import website_loader
|
||||||
from bookmarks.services.bookmarks import create_bookmark, update_bookmark, archive_bookmark, archive_bookmarks, \
|
from bookmarks.services.bookmarks import (
|
||||||
unarchive_bookmark, unarchive_bookmarks, delete_bookmarks, tag_bookmarks, untag_bookmarks, mark_bookmarks_as_read, \
|
create_bookmark,
|
||||||
mark_bookmarks_as_unread, share_bookmarks, unshare_bookmarks
|
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.services.website_loader import WebsiteMetadata
|
||||||
from bookmarks.tests.helpers import BookmarkFactoryMixin
|
from bookmarks.tests.helpers import BookmarkFactoryMixin
|
||||||
|
|
||||||
|
@ -22,36 +34,48 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
self.get_or_create_test_user()
|
self.get_or_create_test_user()
|
||||||
|
|
||||||
def test_create_should_update_website_metadata(self):
|
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(
|
expected_metadata = WebsiteMetadata(
|
||||||
'https://example.com',
|
"https://example.com", "Website title", "Website description"
|
||||||
'Website title',
|
|
||||||
'Website description'
|
|
||||||
)
|
)
|
||||||
mock_load_website_metadata.return_value = expected_metadata
|
mock_load_website_metadata.return_value = expected_metadata
|
||||||
|
|
||||||
bookmark_data = Bookmark(url='https://example.com',
|
bookmark_data = Bookmark(
|
||||||
title='Updated Title',
|
url="https://example.com",
|
||||||
description='Updated description',
|
title="Updated Title",
|
||||||
|
description="Updated description",
|
||||||
unread=True,
|
unread=True,
|
||||||
shared=True,
|
shared=True,
|
||||||
is_archived=True)
|
is_archived=True,
|
||||||
created_bookmark = create_bookmark(bookmark_data, '', self.get_or_create_test_user())
|
)
|
||||||
|
created_bookmark = create_bookmark(
|
||||||
|
bookmark_data, "", self.get_or_create_test_user()
|
||||||
|
)
|
||||||
|
|
||||||
created_bookmark.refresh_from_db()
|
created_bookmark.refresh_from_db()
|
||||||
self.assertEqual(expected_metadata.title, created_bookmark.website_title)
|
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):
|
def test_create_should_update_existing_bookmark_with_same_url(self):
|
||||||
original_bookmark = self.setup_bookmark(url='https://example.com', unread=False, shared=False)
|
original_bookmark = self.setup_bookmark(
|
||||||
bookmark_data = Bookmark(url='https://example.com',
|
url="https://example.com", unread=False, shared=False
|
||||||
title='Updated Title',
|
)
|
||||||
description='Updated description',
|
bookmark_data = Bookmark(
|
||||||
notes='Updated notes',
|
url="https://example.com",
|
||||||
|
title="Updated Title",
|
||||||
|
description="Updated description",
|
||||||
|
notes="Updated notes",
|
||||||
unread=True,
|
unread=True,
|
||||||
shared=True,
|
shared=True,
|
||||||
is_archived=True)
|
is_archived=True,
|
||||||
updated_bookmark = create_bookmark(bookmark_data, '', self.get_or_create_test_user())
|
)
|
||||||
|
updated_bookmark = create_bookmark(
|
||||||
|
bookmark_data, "", self.get_or_create_test_user()
|
||||||
|
)
|
||||||
|
|
||||||
self.assertEqual(Bookmark.objects.count(), 1)
|
self.assertEqual(Bookmark.objects.count(), 1)
|
||||||
self.assertEqual(updated_bookmark.id, original_bookmark.id)
|
self.assertEqual(updated_bookmark.id, original_bookmark.id)
|
||||||
|
@ -64,75 +88,91 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
self.assertFalse(updated_bookmark.is_archived)
|
self.assertFalse(updated_bookmark.is_archived)
|
||||||
|
|
||||||
def test_create_should_create_web_archive_snapshot(self):
|
def test_create_should_create_web_archive_snapshot(self):
|
||||||
with patch.object(tasks, 'create_web_archive_snapshot') as mock_create_web_archive_snapshot:
|
with patch.object(
|
||||||
bookmark_data = Bookmark(url='https://example.com')
|
tasks, "create_web_archive_snapshot"
|
||||||
bookmark = create_bookmark(bookmark_data, 'tag1,tag2', self.user)
|
) 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):
|
def test_create_should_load_favicon(self):
|
||||||
with patch.object(tasks, 'load_favicon') as mock_load_favicon:
|
with patch.object(tasks, "load_favicon") as mock_load_favicon:
|
||||||
bookmark_data = Bookmark(url='https://example.com')
|
bookmark_data = Bookmark(url="https://example.com")
|
||||||
bookmark = create_bookmark(bookmark_data, 'tag1,tag2', self.user)
|
bookmark = create_bookmark(bookmark_data, "tag1,tag2", self.user)
|
||||||
|
|
||||||
mock_load_favicon.assert_called_once_with(self.user, bookmark)
|
mock_load_favicon.assert_called_once_with(self.user, bookmark)
|
||||||
|
|
||||||
def test_update_should_create_web_archive_snapshot_if_url_did_change(self):
|
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 = self.setup_bookmark()
|
||||||
bookmark.url = 'https://example.com/updated'
|
bookmark.url = "https://example.com/updated"
|
||||||
update_bookmark(bookmark, 'tag1,tag2', self.user)
|
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):
|
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 = self.setup_bookmark()
|
||||||
bookmark.title = 'updated title'
|
bookmark.title = "updated title"
|
||||||
update_bookmark(bookmark, 'tag1,tag2', self.user)
|
update_bookmark(bookmark, "tag1,tag2", self.user)
|
||||||
|
|
||||||
mock_create_web_archive_snapshot.assert_not_called()
|
mock_create_web_archive_snapshot.assert_not_called()
|
||||||
|
|
||||||
def test_update_should_update_website_metadata_if_url_did_change(self):
|
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(
|
expected_metadata = WebsiteMetadata(
|
||||||
'https://example.com/updated',
|
"https://example.com/updated",
|
||||||
'Updated website title',
|
"Updated website title",
|
||||||
'Updated website description'
|
"Updated website description",
|
||||||
)
|
)
|
||||||
mock_load_website_metadata.return_value = expected_metadata
|
mock_load_website_metadata.return_value = expected_metadata
|
||||||
|
|
||||||
bookmark = self.setup_bookmark()
|
bookmark = self.setup_bookmark()
|
||||||
bookmark.url = 'https://example.com/updated'
|
bookmark.url = "https://example.com/updated"
|
||||||
update_bookmark(bookmark, 'tag1,tag2', self.user)
|
update_bookmark(bookmark, "tag1,tag2", self.user)
|
||||||
|
|
||||||
bookmark.refresh_from_db()
|
bookmark.refresh_from_db()
|
||||||
mock_load_website_metadata.assert_called_once()
|
mock_load_website_metadata.assert_called_once()
|
||||||
self.assertEqual(expected_metadata.title, bookmark.website_title)
|
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):
|
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 = self.setup_bookmark()
|
||||||
bookmark.title = 'updated title'
|
bookmark.title = "updated title"
|
||||||
update_bookmark(bookmark, 'tag1,tag2', self.user)
|
update_bookmark(bookmark, "tag1,tag2", self.user)
|
||||||
|
|
||||||
mock_load_website_metadata.assert_not_called()
|
mock_load_website_metadata.assert_not_called()
|
||||||
|
|
||||||
def test_update_should_update_favicon(self):
|
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 = self.setup_bookmark()
|
||||||
bookmark.title = 'updated title'
|
bookmark.title = "updated title"
|
||||||
update_bookmark(bookmark, 'tag1,tag2', self.user)
|
update_bookmark(bookmark, "tag1,tag2", self.user)
|
||||||
|
|
||||||
mock_load_favicon.assert_called_once_with(self.user, bookmark)
|
mock_load_favicon.assert_called_once_with(self.user, bookmark)
|
||||||
|
|
||||||
def test_archive_bookmark(self):
|
def test_archive_bookmark(self):
|
||||||
bookmark = Bookmark(
|
bookmark = Bookmark(
|
||||||
url='https://example.com',
|
url="https://example.com",
|
||||||
date_added=timezone.now(),
|
date_added=timezone.now(),
|
||||||
date_modified=timezone.now(),
|
date_modified=timezone.now(),
|
||||||
owner=self.user
|
owner=self.user,
|
||||||
)
|
)
|
||||||
bookmark.save()
|
bookmark.save()
|
||||||
|
|
||||||
|
@ -146,7 +186,7 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
|
|
||||||
def test_unarchive_bookmark(self):
|
def test_unarchive_bookmark(self):
|
||||||
bookmark = Bookmark(
|
bookmark = Bookmark(
|
||||||
url='https://example.com',
|
url="https://example.com",
|
||||||
date_added=timezone.now(),
|
date_added=timezone.now(),
|
||||||
date_modified=timezone.now(),
|
date_modified=timezone.now(),
|
||||||
owner=self.user,
|
owner=self.user,
|
||||||
|
@ -165,7 +205,9 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
bookmark2 = self.setup_bookmark()
|
bookmark2 = self.setup_bookmark()
|
||||||
bookmark3 = 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=bookmark1.id).is_archived)
|
||||||
self.assertTrue(Bookmark.objects.get(id=bookmark2.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)
|
self.assertTrue(Bookmark.objects.get(id=bookmark3.id).is_archived)
|
||||||
|
|
||||||
def test_archive_bookmarks_should_only_archive_user_owned_bookmarks(self):
|
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()
|
bookmark1 = self.setup_bookmark()
|
||||||
bookmark2 = self.setup_bookmark()
|
bookmark2 = self.setup_bookmark()
|
||||||
inaccessible_bookmark = self.setup_bookmark(user=other_user)
|
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=bookmark1.id).is_archived)
|
||||||
self.assertTrue(Bookmark.objects.get(id=bookmark2.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()
|
bookmark2 = self.setup_bookmark()
|
||||||
bookmark3 = 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=bookmark1.id).is_archived)
|
||||||
self.assertTrue(Bookmark.objects.get(id=bookmark2.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)
|
bookmark2 = self.setup_bookmark(is_archived=True)
|
||||||
bookmark3 = 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=bookmark1.id).is_archived)
|
||||||
self.assertFalse(Bookmark.objects.get(id=bookmark2.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)
|
bookmark2 = self.setup_bookmark(is_archived=True)
|
||||||
bookmark3 = 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.assertFalse(Bookmark.objects.get(id=bookmark1.id).is_archived)
|
||||||
self.assertTrue(Bookmark.objects.get(id=bookmark2.id).is_archived)
|
self.assertTrue(Bookmark.objects.get(id=bookmark2.id).is_archived)
|
||||||
self.assertFalse(Bookmark.objects.get(id=bookmark3.id).is_archived)
|
self.assertFalse(Bookmark.objects.get(id=bookmark3.id).is_archived)
|
||||||
|
|
||||||
def test_unarchive_bookmarks_should_only_unarchive_user_owned_bookmarks(self):
|
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)
|
bookmark1 = self.setup_bookmark(is_archived=True)
|
||||||
bookmark2 = self.setup_bookmark(is_archived=True)
|
bookmark2 = self.setup_bookmark(is_archived=True)
|
||||||
inaccessible_bookmark = self.setup_bookmark(is_archived=True, user=other_user)
|
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=bookmark1.id).is_archived)
|
||||||
self.assertFalse(Bookmark.objects.get(id=bookmark2.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)
|
bookmark2 = self.setup_bookmark(is_archived=True)
|
||||||
bookmark3 = 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=bookmark1.id).is_archived)
|
||||||
self.assertFalse(Bookmark.objects.get(id=bookmark2.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()
|
bookmark2 = self.setup_bookmark()
|
||||||
bookmark3 = 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=bookmark1.id).first())
|
||||||
self.assertIsNone(Bookmark.objects.filter(id=bookmark2.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())
|
self.assertIsNone(Bookmark.objects.filter(id=bookmark3.id).first())
|
||||||
|
|
||||||
def test_delete_bookmarks_should_only_delete_user_owned_bookmarks(self):
|
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()
|
bookmark1 = self.setup_bookmark()
|
||||||
bookmark2 = self.setup_bookmark()
|
bookmark2 = self.setup_bookmark()
|
||||||
inaccessible_bookmark = self.setup_bookmark(user=other_user)
|
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=bookmark1.id).first())
|
||||||
self.assertIsNone(Bookmark.objects.filter(id=bookmark2.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):
|
def test_delete_bookmarks_should_accept_mix_of_int_and_string_ids(self):
|
||||||
bookmark1 = self.setup_bookmark()
|
bookmark1 = self.setup_bookmark()
|
||||||
bookmark2 = self.setup_bookmark()
|
bookmark2 = self.setup_bookmark()
|
||||||
bookmark3 = 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=bookmark1.id).first())
|
||||||
self.assertIsNone(Bookmark.objects.filter(id=bookmark2.id).first())
|
self.assertIsNone(Bookmark.objects.filter(id=bookmark2.id).first())
|
||||||
|
@ -302,8 +375,11 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
tag1 = self.setup_tag()
|
tag1 = self.setup_tag()
|
||||||
tag2 = self.setup_tag()
|
tag2 = self.setup_tag()
|
||||||
|
|
||||||
tag_bookmarks([bookmark1.id, bookmark2.id, bookmark3.id], f'{tag1.name},{tag2.name}',
|
tag_bookmarks(
|
||||||
self.get_or_create_test_user())
|
[bookmark1.id, bookmark2.id, bookmark3.id],
|
||||||
|
f"{tag1.name},{tag2.name}",
|
||||||
|
self.get_or_create_test_user(),
|
||||||
|
)
|
||||||
|
|
||||||
bookmark1.refresh_from_db()
|
bookmark1.refresh_from_db()
|
||||||
bookmark2.refresh_from_db()
|
bookmark2.refresh_from_db()
|
||||||
|
@ -318,7 +394,11 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
bookmark2 = self.setup_bookmark()
|
bookmark2 = self.setup_bookmark()
|
||||||
bookmark3 = 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()
|
bookmark1.refresh_from_db()
|
||||||
bookmark2.refresh_from_db()
|
bookmark2.refresh_from_db()
|
||||||
|
@ -326,8 +406,8 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
|
|
||||||
self.assertEqual(2, Tag.objects.count())
|
self.assertEqual(2, Tag.objects.count())
|
||||||
|
|
||||||
tag1 = Tag.objects.filter(name='tag1').first()
|
tag1 = Tag.objects.filter(name="tag1").first()
|
||||||
tag2 = Tag.objects.filter(name='tag2').first()
|
tag2 = Tag.objects.filter(name="tag2").first()
|
||||||
|
|
||||||
self.assertIsNotNone(tag1)
|
self.assertIsNotNone(tag1)
|
||||||
self.assertIsNotNone(tag2)
|
self.assertIsNotNone(tag2)
|
||||||
|
@ -346,8 +426,11 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
BookmarkToTagRelationShip = Bookmark.tags.through
|
BookmarkToTagRelationShip = Bookmark.tags.through
|
||||||
self.assertEqual(3, BookmarkToTagRelationShip.objects.count())
|
self.assertEqual(3, BookmarkToTagRelationShip.objects.count())
|
||||||
|
|
||||||
tag_bookmarks([bookmark1.id, bookmark2.id, bookmark3.id], f'{tag1.name},{tag2.name}',
|
tag_bookmarks(
|
||||||
self.get_or_create_test_user())
|
[bookmark1.id, bookmark2.id, bookmark3.id],
|
||||||
|
f"{tag1.name},{tag2.name}",
|
||||||
|
self.get_or_create_test_user(),
|
||||||
|
)
|
||||||
|
|
||||||
bookmark1.refresh_from_db()
|
bookmark1.refresh_from_db()
|
||||||
bookmark2.refresh_from_db()
|
bookmark2.refresh_from_db()
|
||||||
|
@ -365,7 +448,11 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
tag1 = self.setup_tag()
|
tag1 = self.setup_tag()
|
||||||
tag2 = 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()
|
bookmark1.refresh_from_db()
|
||||||
bookmark2.refresh_from_db()
|
bookmark2.refresh_from_db()
|
||||||
|
@ -376,15 +463,20 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
self.assertCountEqual(bookmark3.tags.all(), [tag1, tag2])
|
self.assertCountEqual(bookmark3.tags.all(), [tag1, tag2])
|
||||||
|
|
||||||
def test_tag_bookmarks_should_only_tag_user_owned_bookmarks(self):
|
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()
|
bookmark1 = self.setup_bookmark()
|
||||||
bookmark2 = self.setup_bookmark()
|
bookmark2 = self.setup_bookmark()
|
||||||
inaccessible_bookmark = self.setup_bookmark(user=other_user)
|
inaccessible_bookmark = self.setup_bookmark(user=other_user)
|
||||||
tag1 = self.setup_tag()
|
tag1 = self.setup_tag()
|
||||||
tag2 = self.setup_tag()
|
tag2 = self.setup_tag()
|
||||||
|
|
||||||
tag_bookmarks([bookmark1.id, bookmark2.id, inaccessible_bookmark.id], f'{tag1.name},{tag2.name}',
|
tag_bookmarks(
|
||||||
self.get_or_create_test_user())
|
[bookmark1.id, bookmark2.id, inaccessible_bookmark.id],
|
||||||
|
f"{tag1.name},{tag2.name}",
|
||||||
|
self.get_or_create_test_user(),
|
||||||
|
)
|
||||||
|
|
||||||
bookmark1.refresh_from_db()
|
bookmark1.refresh_from_db()
|
||||||
bookmark2.refresh_from_db()
|
bookmark2.refresh_from_db()
|
||||||
|
@ -401,8 +493,11 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
tag1 = self.setup_tag()
|
tag1 = self.setup_tag()
|
||||||
tag2 = self.setup_tag()
|
tag2 = self.setup_tag()
|
||||||
|
|
||||||
tag_bookmarks([str(bookmark1.id), bookmark2.id, str(bookmark3.id)], f'{tag1.name},{tag2.name}',
|
tag_bookmarks(
|
||||||
self.get_or_create_test_user())
|
[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(bookmark1.tags.all(), [tag1, tag2])
|
||||||
self.assertCountEqual(bookmark2.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])
|
bookmark2 = self.setup_bookmark(tags=[tag1, tag2])
|
||||||
bookmark3 = 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}',
|
untag_bookmarks(
|
||||||
self.get_or_create_test_user())
|
[bookmark1.id, bookmark2.id, bookmark3.id],
|
||||||
|
f"{tag1.name},{tag2.name}",
|
||||||
|
self.get_or_create_test_user(),
|
||||||
|
)
|
||||||
|
|
||||||
bookmark1.refresh_from_db()
|
bookmark1.refresh_from_db()
|
||||||
bookmark2.refresh_from_db()
|
bookmark2.refresh_from_db()
|
||||||
|
@ -433,7 +531,11 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
bookmark2 = self.setup_bookmark(tags=[tag1, tag2])
|
bookmark2 = self.setup_bookmark(tags=[tag1, tag2])
|
||||||
bookmark3 = 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()
|
bookmark1.refresh_from_db()
|
||||||
bookmark2.refresh_from_db()
|
bookmark2.refresh_from_db()
|
||||||
|
@ -444,15 +546,20 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
self.assertCountEqual(bookmark3.tags.all(), [])
|
self.assertCountEqual(bookmark3.tags.all(), [])
|
||||||
|
|
||||||
def test_untag_bookmarks_should_only_tag_user_owned_bookmarks(self):
|
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()
|
tag1 = self.setup_tag()
|
||||||
tag2 = self.setup_tag()
|
tag2 = self.setup_tag()
|
||||||
bookmark1 = self.setup_bookmark(tags=[tag1, tag2])
|
bookmark1 = self.setup_bookmark(tags=[tag1, tag2])
|
||||||
bookmark2 = self.setup_bookmark(tags=[tag1, tag2])
|
bookmark2 = self.setup_bookmark(tags=[tag1, tag2])
|
||||||
inaccessible_bookmark = self.setup_bookmark(user=other_user, 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}',
|
untag_bookmarks(
|
||||||
self.get_or_create_test_user())
|
[bookmark1.id, bookmark2.id, inaccessible_bookmark.id],
|
||||||
|
f"{tag1.name},{tag2.name}",
|
||||||
|
self.get_or_create_test_user(),
|
||||||
|
)
|
||||||
|
|
||||||
bookmark1.refresh_from_db()
|
bookmark1.refresh_from_db()
|
||||||
bookmark2.refresh_from_db()
|
bookmark2.refresh_from_db()
|
||||||
|
@ -469,8 +576,11 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
bookmark2 = self.setup_bookmark(tags=[tag1, tag2])
|
bookmark2 = self.setup_bookmark(tags=[tag1, tag2])
|
||||||
bookmark3 = 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}',
|
untag_bookmarks(
|
||||||
self.get_or_create_test_user())
|
[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(bookmark1.tags.all(), [])
|
||||||
self.assertCountEqual(bookmark2.tags.all(), [])
|
self.assertCountEqual(bookmark2.tags.all(), [])
|
||||||
|
@ -481,7 +591,9 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
bookmark2 = self.setup_bookmark(unread=True)
|
bookmark2 = self.setup_bookmark(unread=True)
|
||||||
bookmark3 = 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=bookmark1.id).unread)
|
||||||
self.assertFalse(Bookmark.objects.get(id=bookmark2.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)
|
bookmark2 = self.setup_bookmark(unread=True)
|
||||||
bookmark3 = 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.assertFalse(Bookmark.objects.get(id=bookmark1.id).unread)
|
||||||
self.assertTrue(Bookmark.objects.get(id=bookmark2.id).unread)
|
self.assertTrue(Bookmark.objects.get(id=bookmark2.id).unread)
|
||||||
self.assertFalse(Bookmark.objects.get(id=bookmark3.id).unread)
|
self.assertFalse(Bookmark.objects.get(id=bookmark3.id).unread)
|
||||||
|
|
||||||
def test_mark_bookmarks_as_read_should_only_update_user_owned_bookmarks(self):
|
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)
|
bookmark1 = self.setup_bookmark(unread=True)
|
||||||
bookmark2 = self.setup_bookmark(unread=True)
|
bookmark2 = self.setup_bookmark(unread=True)
|
||||||
inaccessible_bookmark = self.setup_bookmark(unread=True, user=other_user)
|
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=bookmark1.id).unread)
|
||||||
self.assertFalse(Bookmark.objects.get(id=bookmark2.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)
|
bookmark2 = self.setup_bookmark(unread=True)
|
||||||
bookmark3 = 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=bookmark1.id).unread)
|
||||||
self.assertFalse(Bookmark.objects.get(id=bookmark2.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)
|
bookmark2 = self.setup_bookmark(unread=False)
|
||||||
bookmark3 = 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=bookmark1.id).unread)
|
||||||
self.assertTrue(Bookmark.objects.get(id=bookmark2.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)
|
bookmark2 = self.setup_bookmark(unread=False)
|
||||||
bookmark3 = 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.assertTrue(Bookmark.objects.get(id=bookmark1.id).unread)
|
||||||
self.assertFalse(Bookmark.objects.get(id=bookmark2.id).unread)
|
self.assertFalse(Bookmark.objects.get(id=bookmark2.id).unread)
|
||||||
self.assertTrue(Bookmark.objects.get(id=bookmark3.id).unread)
|
self.assertTrue(Bookmark.objects.get(id=bookmark3.id).unread)
|
||||||
|
|
||||||
def test_mark_bookmarks_as_unread_should_only_update_user_owned_bookmarks(self):
|
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)
|
bookmark1 = self.setup_bookmark(unread=False)
|
||||||
bookmark2 = self.setup_bookmark(unread=False)
|
bookmark2 = self.setup_bookmark(unread=False)
|
||||||
inaccessible_bookmark = self.setup_bookmark(unread=False, user=other_user)
|
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=bookmark1.id).unread)
|
||||||
self.assertTrue(Bookmark.objects.get(id=bookmark2.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)
|
bookmark2 = self.setup_bookmark(unread=False)
|
||||||
bookmark3 = 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=bookmark1.id).unread)
|
||||||
self.assertTrue(Bookmark.objects.get(id=bookmark2.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)
|
bookmark2 = self.setup_bookmark(shared=False)
|
||||||
bookmark3 = 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=bookmark1.id).shared)
|
||||||
self.assertTrue(Bookmark.objects.get(id=bookmark2.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)
|
self.assertTrue(Bookmark.objects.get(id=bookmark3.id).shared)
|
||||||
|
|
||||||
def test_share_bookmarks_should_only_update_user_owned_bookmarks(self):
|
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)
|
bookmark1 = self.setup_bookmark(shared=False)
|
||||||
bookmark2 = self.setup_bookmark(shared=False)
|
bookmark2 = self.setup_bookmark(shared=False)
|
||||||
inaccessible_bookmark = self.setup_bookmark(shared=False, user=other_user)
|
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=bookmark1.id).shared)
|
||||||
self.assertTrue(Bookmark.objects.get(id=bookmark2.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)
|
bookmark2 = self.setup_bookmark(shared=False)
|
||||||
bookmark3 = 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=bookmark1.id).shared)
|
||||||
self.assertTrue(Bookmark.objects.get(id=bookmark2.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)
|
bookmark2 = self.setup_bookmark(shared=True)
|
||||||
bookmark3 = 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=bookmark1.id).shared)
|
||||||
self.assertFalse(Bookmark.objects.get(id=bookmark2.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)
|
self.assertFalse(Bookmark.objects.get(id=bookmark3.id).shared)
|
||||||
|
|
||||||
def test_unshare_bookmarks_should_only_update_user_owned_bookmarks(self):
|
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)
|
bookmark1 = self.setup_bookmark(shared=True)
|
||||||
bookmark2 = self.setup_bookmark(shared=True)
|
bookmark2 = self.setup_bookmark(shared=True)
|
||||||
inaccessible_bookmark = self.setup_bookmark(shared=True, user=other_user)
|
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=bookmark1.id).shared)
|
||||||
self.assertFalse(Bookmark.objects.get(id=bookmark2.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)
|
bookmark2 = self.setup_bookmark(shared=True)
|
||||||
bookmark3 = 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=bookmark1.id).shared)
|
||||||
self.assertFalse(Bookmark.objects.get(id=bookmark2.id).shared)
|
self.assertFalse(Bookmark.objects.get(id=bookmark2.id).shared)
|
||||||
|
|
|
@ -16,8 +16,10 @@ from bookmarks.services import tasks
|
||||||
from bookmarks.tests.helpers import BookmarkFactoryMixin, disable_logging
|
from bookmarks.tests.helpers import BookmarkFactoryMixin, disable_logging
|
||||||
|
|
||||||
|
|
||||||
def create_wayback_machine_save_api_mock(archive_url: str = 'https://example.com/created_snapshot',
|
def create_wayback_machine_save_api_mock(
|
||||||
fail_on_save: bool = False):
|
archive_url: str = "https://example.com/created_snapshot",
|
||||||
|
fail_on_save: bool = False,
|
||||||
|
):
|
||||||
mock_api = mock.Mock(archive_url=archive_url)
|
mock_api = mock.Mock(archive_url=archive_url)
|
||||||
|
|
||||||
if fail_on_save:
|
if fail_on_save:
|
||||||
|
@ -32,14 +34,18 @@ class MockCdxSnapshot:
|
||||||
datetime_timestamp: datetime.datetime
|
datetime_timestamp: datetime.datetime
|
||||||
|
|
||||||
|
|
||||||
def create_cdx_server_api_mock(archive_url: str | None = 'https://example.com/newest_snapshot',
|
def create_cdx_server_api_mock(
|
||||||
fail_loading_snapshot=False):
|
archive_url: str | None = "https://example.com/newest_snapshot",
|
||||||
|
fail_loading_snapshot=False,
|
||||||
|
):
|
||||||
mock_api = mock.Mock()
|
mock_api = mock.Mock()
|
||||||
|
|
||||||
if fail_loading_snapshot:
|
if fail_loading_snapshot:
|
||||||
mock_api.newest.side_effect = WaybackError
|
mock_api.newest.side_effect = WaybackError
|
||||||
elif archive_url:
|
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:
|
else:
|
||||||
mock_api.newest.return_value = None
|
mock_api.newest.return_value = None
|
||||||
|
|
||||||
|
@ -50,13 +56,15 @@ class BookmarkTasksTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
user = self.get_or_create_test_user()
|
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.enable_favicons = True
|
||||||
user.profile.save()
|
user.profile.save()
|
||||||
|
|
||||||
@disable_logging
|
@disable_logging
|
||||||
def run_pending_task(self, task_function: Any):
|
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]
|
task = Task.objects.all()[0]
|
||||||
self.assertEqual(task_function.name, task.task_name)
|
self.assertEqual(task_function.name, task.task_name)
|
||||||
args, kwargs = task.params()
|
args, kwargs = task.params()
|
||||||
|
@ -65,7 +73,7 @@ class BookmarkTasksTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
|
|
||||||
@disable_logging
|
@disable_logging
|
||||||
def run_all_pending_tasks(self, task_function: Any):
|
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()
|
tasks = Task.objects.all()
|
||||||
|
|
||||||
for task in tasks:
|
for task in tasks:
|
||||||
|
@ -78,86 +86,129 @@ class BookmarkTasksTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
bookmark = self.setup_bookmark()
|
bookmark = self.setup_bookmark()
|
||||||
mock_save_api = create_wayback_machine_save_api_mock()
|
mock_save_api = create_wayback_machine_save_api_mock()
|
||||||
|
|
||||||
with mock.patch.object(waybackpy, 'WaybackMachineSaveAPI', return_value=mock_save_api):
|
with mock.patch.object(
|
||||||
tasks.create_web_archive_snapshot(self.get_or_create_test_user(), bookmark, False)
|
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)
|
self.run_pending_task(tasks._create_web_archive_snapshot_task)
|
||||||
bookmark.refresh_from_db()
|
bookmark.refresh_from_db()
|
||||||
|
|
||||||
mock_save_api.save.assert_called_once()
|
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):
|
def test_create_web_archive_snapshot_should_handle_missing_bookmark_id(self):
|
||||||
mock_save_api = create_wayback_machine_save_api_mock()
|
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)
|
tasks._create_web_archive_snapshot_task(123, False)
|
||||||
self.run_pending_task(tasks._create_web_archive_snapshot_task)
|
self.run_pending_task(tasks._create_web_archive_snapshot_task)
|
||||||
|
|
||||||
mock_save_api.save.assert_not_called()
|
mock_save_api.save.assert_not_called()
|
||||||
|
|
||||||
def test_create_web_archive_snapshot_should_skip_if_snapshot_exists(self):
|
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()
|
mock_save_api = create_wayback_machine_save_api_mock()
|
||||||
|
|
||||||
with mock.patch.object(waybackpy, 'WaybackMachineSaveAPI', return_value=mock_save_api):
|
with mock.patch.object(
|
||||||
tasks.create_web_archive_snapshot(self.get_or_create_test_user(), bookmark, False)
|
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)
|
self.run_pending_task(tasks._create_web_archive_snapshot_task)
|
||||||
|
|
||||||
mock_save_api.assert_not_called()
|
mock_save_api.assert_not_called()
|
||||||
|
|
||||||
def test_create_web_archive_snapshot_should_force_update_snapshot(self):
|
def test_create_web_archive_snapshot_should_force_update_snapshot(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(archive_url='https://other.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):
|
with mock.patch.object(
|
||||||
tasks.create_web_archive_snapshot(self.get_or_create_test_user(), bookmark, True)
|
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)
|
self.run_pending_task(tasks._create_web_archive_snapshot_task)
|
||||||
bookmark.refresh_from_db()
|
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):
|
def test_create_web_archive_snapshot_should_use_newest_snapshot_as_fallback(self):
|
||||||
bookmark = self.setup_bookmark()
|
bookmark = self.setup_bookmark()
|
||||||
mock_save_api = create_wayback_machine_save_api_mock(fail_on_save=True)
|
mock_save_api = create_wayback_machine_save_api_mock(fail_on_save=True)
|
||||||
mock_cdx_api = create_cdx_server_api_mock()
|
mock_cdx_api = create_cdx_server_api_mock()
|
||||||
|
|
||||||
with mock.patch.object(waybackpy, 'WaybackMachineSaveAPI', return_value=mock_save_api):
|
with mock.patch.object(
|
||||||
with mock.patch.object(bookmarks.services.wayback, 'CustomWaybackMachineCDXServerAPI',
|
waybackpy, "WaybackMachineSaveAPI", return_value=mock_save_api
|
||||||
return_value=mock_cdx_api):
|
):
|
||||||
tasks.create_web_archive_snapshot(self.get_or_create_test_user(), bookmark, False)
|
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)
|
self.run_pending_task(tasks._create_web_archive_snapshot_task)
|
||||||
|
|
||||||
bookmark.refresh_from_db()
|
bookmark.refresh_from_db()
|
||||||
mock_cdx_api.newest.assert_called_once()
|
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):
|
def test_create_web_archive_snapshot_should_ignore_missing_newest_snapshot(self):
|
||||||
bookmark = self.setup_bookmark()
|
bookmark = self.setup_bookmark()
|
||||||
mock_save_api = create_wayback_machine_save_api_mock(fail_on_save=True)
|
mock_save_api = create_wayback_machine_save_api_mock(fail_on_save=True)
|
||||||
mock_cdx_api = create_cdx_server_api_mock(archive_url=None)
|
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(
|
||||||
with mock.patch.object(bookmarks.services.wayback, 'CustomWaybackMachineCDXServerAPI',
|
waybackpy, "WaybackMachineSaveAPI", return_value=mock_save_api
|
||||||
return_value=mock_cdx_api):
|
):
|
||||||
tasks.create_web_archive_snapshot(self.get_or_create_test_user(), bookmark, False)
|
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)
|
self.run_pending_task(tasks._create_web_archive_snapshot_task)
|
||||||
|
|
||||||
bookmark.refresh_from_db()
|
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):
|
def test_create_web_archive_snapshot_should_ignore_newest_snapshot_errors(self):
|
||||||
bookmark = self.setup_bookmark()
|
bookmark = self.setup_bookmark()
|
||||||
mock_save_api = create_wayback_machine_save_api_mock(fail_on_save=True)
|
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)
|
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(
|
||||||
with mock.patch.object(bookmarks.services.wayback, 'CustomWaybackMachineCDXServerAPI',
|
waybackpy, "WaybackMachineSaveAPI", return_value=mock_save_api
|
||||||
return_value=mock_cdx_api):
|
):
|
||||||
tasks.create_web_archive_snapshot(self.get_or_create_test_user(), bookmark, False)
|
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)
|
self.run_pending_task(tasks._create_web_archive_snapshot_task)
|
||||||
|
|
||||||
bookmark.refresh_from_db()
|
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):
|
def test_create_web_archive_snapshot_should_not_save_stale_bookmark_data(self):
|
||||||
bookmark = self.setup_bookmark()
|
bookmark = self.setup_bookmark()
|
||||||
|
@ -166,48 +217,66 @@ class BookmarkTasksTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
# update bookmark during API call to check that saving
|
# update bookmark during API call to check that saving
|
||||||
# the snapshot does not overwrite updated bookmark data
|
# the snapshot does not overwrite updated bookmark data
|
||||||
def mock_save_impl():
|
def mock_save_impl():
|
||||||
bookmark.title = 'Updated title'
|
bookmark.title = "Updated title"
|
||||||
bookmark.save()
|
bookmark.save()
|
||||||
|
|
||||||
mock_save_api.save.side_effect = mock_save_impl
|
mock_save_api.save.side_effect = mock_save_impl
|
||||||
|
|
||||||
with mock.patch.object(waybackpy, 'WaybackMachineSaveAPI', return_value=mock_save_api):
|
with mock.patch.object(
|
||||||
tasks.create_web_archive_snapshot(self.get_or_create_test_user(), bookmark, False)
|
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)
|
self.run_pending_task(tasks._create_web_archive_snapshot_task)
|
||||||
bookmark.refresh_from_db()
|
bookmark.refresh_from_db()
|
||||||
|
|
||||||
self.assertEqual(bookmark.title, 'Updated title')
|
self.assertEqual(bookmark.title, "Updated title")
|
||||||
self.assertEqual('https://example.com/created_snapshot', bookmark.web_archive_snapshot_url)
|
self.assertEqual(
|
||||||
|
"https://example.com/created_snapshot",
|
||||||
|
bookmark.web_archive_snapshot_url,
|
||||||
|
)
|
||||||
|
|
||||||
def test_load_web_archive_snapshot_should_update_snapshot_url(self):
|
def test_load_web_archive_snapshot_should_update_snapshot_url(self):
|
||||||
bookmark = self.setup_bookmark()
|
bookmark = self.setup_bookmark()
|
||||||
mock_cdx_api = create_cdx_server_api_mock()
|
mock_cdx_api = create_cdx_server_api_mock()
|
||||||
|
|
||||||
with mock.patch.object(bookmarks.services.wayback, 'CustomWaybackMachineCDXServerAPI',
|
with mock.patch.object(
|
||||||
return_value=mock_cdx_api):
|
bookmarks.services.wayback,
|
||||||
|
"CustomWaybackMachineCDXServerAPI",
|
||||||
|
return_value=mock_cdx_api,
|
||||||
|
):
|
||||||
tasks._load_web_archive_snapshot_task(bookmark.id)
|
tasks._load_web_archive_snapshot_task(bookmark.id)
|
||||||
self.run_pending_task(tasks._load_web_archive_snapshot_task)
|
self.run_pending_task(tasks._load_web_archive_snapshot_task)
|
||||||
|
|
||||||
bookmark.refresh_from_db()
|
bookmark.refresh_from_db()
|
||||||
mock_cdx_api.newest.assert_called_once()
|
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):
|
def test_load_web_archive_snapshot_should_handle_missing_bookmark_id(self):
|
||||||
mock_cdx_api = create_cdx_server_api_mock()
|
mock_cdx_api = create_cdx_server_api_mock()
|
||||||
|
|
||||||
with mock.patch.object(bookmarks.services.wayback, 'CustomWaybackMachineCDXServerAPI',
|
with mock.patch.object(
|
||||||
return_value=mock_cdx_api):
|
bookmarks.services.wayback,
|
||||||
|
"CustomWaybackMachineCDXServerAPI",
|
||||||
|
return_value=mock_cdx_api,
|
||||||
|
):
|
||||||
tasks._load_web_archive_snapshot_task(123)
|
tasks._load_web_archive_snapshot_task(123)
|
||||||
self.run_pending_task(tasks._load_web_archive_snapshot_task)
|
self.run_pending_task(tasks._load_web_archive_snapshot_task)
|
||||||
|
|
||||||
mock_cdx_api.newest.assert_not_called()
|
mock_cdx_api.newest.assert_not_called()
|
||||||
|
|
||||||
def test_load_web_archive_snapshot_should_skip_if_snapshot_exists(self):
|
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()
|
mock_cdx_api = create_cdx_server_api_mock()
|
||||||
|
|
||||||
with mock.patch.object(bookmarks.services.wayback, 'CustomWaybackMachineCDXServerAPI',
|
with mock.patch.object(
|
||||||
return_value=mock_cdx_api):
|
bookmarks.services.wayback,
|
||||||
|
"CustomWaybackMachineCDXServerAPI",
|
||||||
|
return_value=mock_cdx_api,
|
||||||
|
):
|
||||||
tasks._load_web_archive_snapshot_task(bookmark.id)
|
tasks._load_web_archive_snapshot_task(bookmark.id)
|
||||||
self.run_pending_task(tasks._load_web_archive_snapshot_task)
|
self.run_pending_task(tasks._load_web_archive_snapshot_task)
|
||||||
|
|
||||||
|
@ -217,23 +286,29 @@ class BookmarkTasksTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
bookmark = self.setup_bookmark()
|
bookmark = self.setup_bookmark()
|
||||||
mock_cdx_api = create_cdx_server_api_mock(archive_url=None)
|
mock_cdx_api = create_cdx_server_api_mock(archive_url=None)
|
||||||
|
|
||||||
with mock.patch.object(bookmarks.services.wayback, 'CustomWaybackMachineCDXServerAPI',
|
with mock.patch.object(
|
||||||
return_value=mock_cdx_api):
|
bookmarks.services.wayback,
|
||||||
|
"CustomWaybackMachineCDXServerAPI",
|
||||||
|
return_value=mock_cdx_api,
|
||||||
|
):
|
||||||
tasks._load_web_archive_snapshot_task(bookmark.id)
|
tasks._load_web_archive_snapshot_task(bookmark.id)
|
||||||
self.run_pending_task(tasks._load_web_archive_snapshot_task)
|
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):
|
def test_load_web_archive_snapshot_should_handle_wayback_errors(self):
|
||||||
bookmark = self.setup_bookmark()
|
bookmark = self.setup_bookmark()
|
||||||
mock_cdx_api = create_cdx_server_api_mock(fail_loading_snapshot=True)
|
mock_cdx_api = create_cdx_server_api_mock(fail_loading_snapshot=True)
|
||||||
|
|
||||||
with mock.patch.object(bookmarks.services.wayback, 'CustomWaybackMachineCDXServerAPI',
|
with mock.patch.object(
|
||||||
return_value=mock_cdx_api):
|
bookmarks.services.wayback,
|
||||||
|
"CustomWaybackMachineCDXServerAPI",
|
||||||
|
return_value=mock_cdx_api,
|
||||||
|
):
|
||||||
tasks._load_web_archive_snapshot_task(bookmark.id)
|
tasks._load_web_archive_snapshot_task(bookmark.id)
|
||||||
self.run_pending_task(tasks._load_web_archive_snapshot_task)
|
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):
|
def test_load_web_archive_snapshot_should_not_save_stale_bookmark_data(self):
|
||||||
bookmark = self.setup_bookmark()
|
bookmark = self.setup_bookmark()
|
||||||
|
@ -242,45 +317,62 @@ class BookmarkTasksTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
# update bookmark during API call to check that saving
|
# update bookmark during API call to check that saving
|
||||||
# the snapshot does not overwrite updated bookmark data
|
# the snapshot does not overwrite updated bookmark data
|
||||||
def mock_newest_impl():
|
def mock_newest_impl():
|
||||||
bookmark.title = 'Updated title'
|
bookmark.title = "Updated title"
|
||||||
bookmark.save()
|
bookmark.save()
|
||||||
return mock.DEFAULT
|
return mock.DEFAULT
|
||||||
|
|
||||||
mock_cdx_api.newest.side_effect = mock_newest_impl
|
mock_cdx_api.newest.side_effect = mock_newest_impl
|
||||||
|
|
||||||
with mock.patch.object(bookmarks.services.wayback, 'CustomWaybackMachineCDXServerAPI',
|
with mock.patch.object(
|
||||||
return_value=mock_cdx_api):
|
bookmarks.services.wayback,
|
||||||
|
"CustomWaybackMachineCDXServerAPI",
|
||||||
|
return_value=mock_cdx_api,
|
||||||
|
):
|
||||||
tasks._load_web_archive_snapshot_task(bookmark.id)
|
tasks._load_web_archive_snapshot_task(bookmark.id)
|
||||||
self.run_pending_task(tasks._load_web_archive_snapshot_task)
|
self.run_pending_task(tasks._load_web_archive_snapshot_task)
|
||||||
bookmark.refresh_from_db()
|
bookmark.refresh_from_db()
|
||||||
|
|
||||||
self.assertEqual('Updated title', bookmark.title)
|
self.assertEqual("Updated title", bookmark.title)
|
||||||
self.assertEqual('https://example.com/newest_snapshot', bookmark.web_archive_snapshot_url)
|
self.assertEqual(
|
||||||
|
"https://example.com/newest_snapshot", bookmark.web_archive_snapshot_url
|
||||||
|
)
|
||||||
|
|
||||||
@override_settings(LD_DISABLE_BACKGROUND_TASKS=True)
|
@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()
|
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)
|
self.assertEqual(Task.objects.count(), 0)
|
||||||
|
|
||||||
def test_create_web_archive_snapshot_should_not_run_when_web_archive_integration_is_disabled(self):
|
def test_create_web_archive_snapshot_should_not_run_when_web_archive_integration_is_disabled(
|
||||||
self.user.profile.web_archive_integration = UserProfile.WEB_ARCHIVE_INTEGRATION_DISABLED
|
self,
|
||||||
|
):
|
||||||
|
self.user.profile.web_archive_integration = (
|
||||||
|
UserProfile.WEB_ARCHIVE_INTEGRATION_DISABLED
|
||||||
|
)
|
||||||
self.user.profile.save()
|
self.user.profile.save()
|
||||||
|
|
||||||
bookmark = self.setup_bookmark()
|
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)
|
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()
|
user = self.get_or_create_test_user()
|
||||||
self.setup_bookmark()
|
self.setup_bookmark()
|
||||||
self.setup_bookmark()
|
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)
|
tasks.schedule_bookmarks_without_snapshots(user)
|
||||||
self.run_pending_task(tasks._schedule_bookmarks_without_snapshots_task)
|
self.run_pending_task(tasks._schedule_bookmarks_without_snapshots_task)
|
||||||
|
@ -289,11 +381,18 @@ class BookmarkTasksTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
self.assertEqual(task_list.count(), 3)
|
self.assertEqual(task_list.count(), 3)
|
||||||
|
|
||||||
for task in task_list:
|
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()
|
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()
|
self.setup_bookmark()
|
||||||
self.setup_bookmark()
|
self.setup_bookmark()
|
||||||
|
@ -308,13 +407,19 @@ class BookmarkTasksTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
self.assertEqual(task_list.count(), 3)
|
self.assertEqual(task_list.count(), 3)
|
||||||
|
|
||||||
@override_settings(LD_DISABLE_BACKGROUND_TASKS=True)
|
@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)
|
tasks.schedule_bookmarks_without_snapshots(self.user)
|
||||||
|
|
||||||
self.assertEqual(Task.objects.count(), 0)
|
self.assertEqual(Task.objects.count(), 0)
|
||||||
|
|
||||||
def test_schedule_bookmarks_without_snapshots_should_not_run_when_web_archive_integration_is_disabled(self):
|
def test_schedule_bookmarks_without_snapshots_should_not_run_when_web_archive_integration_is_disabled(
|
||||||
self.user.profile.web_archive_integration = UserProfile.WEB_ARCHIVE_INTEGRATION_DISABLED
|
self,
|
||||||
|
):
|
||||||
|
self.user.profile.web_archive_integration = (
|
||||||
|
UserProfile.WEB_ARCHIVE_INTEGRATION_DISABLED
|
||||||
|
)
|
||||||
self.user.profile.save()
|
self.user.profile.save()
|
||||||
tasks.schedule_bookmarks_without_snapshots(self.user)
|
tasks.schedule_bookmarks_without_snapshots(self.user)
|
||||||
|
|
||||||
|
@ -323,29 +428,35 @@ class BookmarkTasksTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
def test_load_favicon_should_create_favicon_file(self):
|
def test_load_favicon_should_create_favicon_file(self):
|
||||||
bookmark = self.setup_bookmark()
|
bookmark = self.setup_bookmark()
|
||||||
|
|
||||||
with mock.patch('bookmarks.services.favicon_loader.load_favicon') as mock_load_favicon:
|
with mock.patch(
|
||||||
mock_load_favicon.return_value = 'https_example_com.png'
|
"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)
|
tasks.load_favicon(self.get_or_create_test_user(), bookmark)
|
||||||
self.run_pending_task(tasks._load_favicon_task)
|
self.run_pending_task(tasks._load_favicon_task)
|
||||||
bookmark.refresh_from_db()
|
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):
|
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:
|
with mock.patch(
|
||||||
mock_load_favicon.return_value = 'https_example_updated_com.png'
|
"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)
|
tasks.load_favicon(self.get_or_create_test_user(), bookmark)
|
||||||
self.run_pending_task(tasks._load_favicon_task)
|
self.run_pending_task(tasks._load_favicon_task)
|
||||||
|
|
||||||
mock_load_favicon.assert_called()
|
mock_load_favicon.assert_called()
|
||||||
bookmark.refresh_from_db()
|
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):
|
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)
|
tasks._load_favicon_task(123)
|
||||||
self.run_pending_task(tasks._load_favicon_task)
|
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
|
# update bookmark during API call to check that saving
|
||||||
# the favicon does not overwrite updated bookmark data
|
# the favicon does not overwrite updated bookmark data
|
||||||
def mock_load_favicon_impl(url):
|
def mock_load_favicon_impl(url):
|
||||||
bookmark.title = 'Updated title'
|
bookmark.title = "Updated title"
|
||||||
bookmark.save()
|
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
|
mock_load_favicon.side_effect = mock_load_favicon_impl
|
||||||
|
|
||||||
tasks.load_favicon(self.get_or_create_test_user(), bookmark)
|
tasks.load_favicon(self.get_or_create_test_user(), bookmark)
|
||||||
self.run_pending_task(tasks._load_favicon_task)
|
self.run_pending_task(tasks._load_favicon_task)
|
||||||
bookmark.refresh_from_db()
|
bookmark.refresh_from_db()
|
||||||
|
|
||||||
self.assertEqual(bookmark.title, 'Updated title')
|
self.assertEqual(bookmark.title, "Updated title")
|
||||||
self.assertEqual(bookmark.favicon_file, 'https_example_com.png')
|
self.assertEqual(bookmark.favicon_file, "https_example_com.png")
|
||||||
|
|
||||||
@override_settings(LD_DISABLE_BACKGROUND_TASKS=True)
|
@override_settings(LD_DISABLE_BACKGROUND_TASKS=True)
|
||||||
def test_load_favicon_should_not_run_when_background_tasks_are_disabled(self):
|
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)
|
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()
|
user = self.get_or_create_test_user()
|
||||||
self.setup_bookmark()
|
self.setup_bookmark()
|
||||||
self.setup_bookmark()
|
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)
|
tasks.schedule_bookmarks_without_favicons(user)
|
||||||
self.run_pending_task(tasks._schedule_bookmarks_without_favicons_task)
|
self.run_pending_task(tasks._schedule_bookmarks_without_favicons_task)
|
||||||
|
@ -403,11 +518,17 @@ class BookmarkTasksTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
self.assertEqual(task_list.count(), 3)
|
self.assertEqual(task_list.count(), 3)
|
||||||
|
|
||||||
for task in task_list:
|
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()
|
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()
|
self.setup_bookmark()
|
||||||
self.setup_bookmark()
|
self.setup_bookmark()
|
||||||
|
@ -422,13 +543,17 @@ class BookmarkTasksTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
self.assertEqual(task_list.count(), 3)
|
self.assertEqual(task_list.count(), 3)
|
||||||
|
|
||||||
@override_settings(LD_DISABLE_BACKGROUND_TASKS=True)
|
@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()
|
bookmark = self.setup_bookmark()
|
||||||
tasks.schedule_bookmarks_without_favicons(self.get_or_create_test_user())
|
tasks.schedule_bookmarks_without_favicons(self.get_or_create_test_user())
|
||||||
|
|
||||||
self.assertEqual(Task.objects.count(), 0)
|
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.enable_favicons = False
|
||||||
self.user.profile.save()
|
self.user.profile.save()
|
||||||
|
|
||||||
|
@ -442,9 +567,9 @@ class BookmarkTasksTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
self.setup_bookmark()
|
self.setup_bookmark()
|
||||||
self.setup_bookmark()
|
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)
|
tasks.schedule_refresh_favicons(user)
|
||||||
self.run_pending_task(tasks._schedule_refresh_favicons_task)
|
self.run_pending_task(tasks._schedule_refresh_favicons_task)
|
||||||
|
@ -453,11 +578,15 @@ class BookmarkTasksTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
self.assertEqual(task_list.count(), 6)
|
self.assertEqual(task_list.count(), 6)
|
||||||
|
|
||||||
for task in task_list:
|
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):
|
def test_schedule_refresh_favicons_should_only_update_user_owned_bookmarks(self):
|
||||||
user = self.get_or_create_test_user()
|
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()
|
self.setup_bookmark()
|
||||||
self.setup_bookmark()
|
self.setup_bookmark()
|
||||||
|
@ -472,7 +601,9 @@ class BookmarkTasksTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
self.assertEqual(task_list.count(), 3)
|
self.assertEqual(task_list.count(), 3)
|
||||||
|
|
||||||
@override_settings(LD_DISABLE_BACKGROUND_TASKS=True)
|
@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()
|
self.setup_bookmark()
|
||||||
tasks.schedule_refresh_favicons(self.get_or_create_test_user())
|
tasks.schedule_refresh_favicons(self.get_or_create_test_user())
|
||||||
|
|
||||||
|
@ -485,7 +616,9 @@ class BookmarkTasksTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
|
|
||||||
self.assertEqual(Task.objects.count(), 0)
|
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.enable_favicons = False
|
||||||
self.user.profile.save()
|
self.user.profile.save()
|
||||||
|
|
||||||
|
|
|
@ -12,40 +12,43 @@ class MockUrlConf:
|
||||||
class ContextPathTestCase(TestCase):
|
class ContextPathTestCase(TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
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)
|
@override_settings(LD_CONTEXT_PATH=None)
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
importlib.reload(self.siteroot_urls)
|
importlib.reload(self.siteroot_urls)
|
||||||
|
|
||||||
@override_settings(LD_CONTEXT_PATH='linkding/')
|
@override_settings(LD_CONTEXT_PATH="linkding/")
|
||||||
def test_route_with_context_path(self):
|
def test_route_with_context_path(self):
|
||||||
module = importlib.reload(self.siteroot_urls)
|
module = importlib.reload(self.siteroot_urls)
|
||||||
# pass mock config instead of actual module to prevent caching the
|
# pass mock config instead of actual module to prevent caching the
|
||||||
# url config in django.urls.reverse
|
# url config in django.urls.reverse
|
||||||
urlconf = MockUrlConf(module)
|
urlconf = MockUrlConf(module)
|
||||||
test_cases = [
|
test_cases = [
|
||||||
('bookmarks:index', '/linkding/bookmarks'),
|
("bookmarks:index", "/linkding/bookmarks"),
|
||||||
('bookmarks:bookmark-list', '/linkding/api/bookmarks/'),
|
("bookmarks:bookmark-list", "/linkding/api/bookmarks/"),
|
||||||
('login', '/linkding/login/'),
|
("login", "/linkding/login/"),
|
||||||
('admin:bookmarks_bookmark_changelist', '/linkding/admin/bookmarks/bookmark/'),
|
(
|
||||||
|
"admin:bookmarks_bookmark_changelist",
|
||||||
|
"/linkding/admin/bookmarks/bookmark/",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
for url_name, expected_url in test_cases:
|
for url_name, expected_url in test_cases:
|
||||||
url = reverse(url_name, urlconf=urlconf)
|
url = reverse(url_name, urlconf=urlconf)
|
||||||
self.assertEqual(expected_url, url)
|
self.assertEqual(expected_url, url)
|
||||||
|
|
||||||
@override_settings(LD_CONTEXT_PATH='')
|
@override_settings(LD_CONTEXT_PATH="")
|
||||||
def test_route_without_context_path(self):
|
def test_route_without_context_path(self):
|
||||||
module = importlib.reload(self.siteroot_urls)
|
module = importlib.reload(self.siteroot_urls)
|
||||||
# pass mock config instead of actual module to prevent caching the
|
# pass mock config instead of actual module to prevent caching the
|
||||||
# url config in django.urls.reverse
|
# url config in django.urls.reverse
|
||||||
urlconf = MockUrlConf(module)
|
urlconf = MockUrlConf(module)
|
||||||
test_cases = [
|
test_cases = [
|
||||||
('bookmarks:index', '/bookmarks'),
|
("bookmarks:index", "/bookmarks"),
|
||||||
('bookmarks:bookmark-list', '/api/bookmarks/'),
|
("bookmarks:bookmark-list", "/api/bookmarks/"),
|
||||||
('login', '/login/'),
|
("login", "/login/"),
|
||||||
('admin:bookmarks_bookmark_changelist', '/admin/bookmarks/bookmark/'),
|
("admin:bookmarks_bookmark_changelist", "/admin/bookmarks/bookmark/"),
|
||||||
]
|
]
|
||||||
|
|
||||||
for url_name, expected_url in test_cases:
|
for url_name, expected_url in test_cases:
|
||||||
|
|
|
@ -9,25 +9,28 @@ from bookmarks.management.commands.create_initial_superuser import Command
|
||||||
|
|
||||||
class TestCreateInitialSuperuserCommand(TestCase):
|
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):
|
def test_create_with_password(self):
|
||||||
Command().handle()
|
Command().handle()
|
||||||
|
|
||||||
self.assertEqual(1, User.objects.count())
|
self.assertEqual(1, User.objects.count())
|
||||||
|
|
||||||
user = User.objects.first()
|
user = User.objects.first()
|
||||||
self.assertEqual('john', user.username)
|
self.assertEqual("john", user.username)
|
||||||
self.assertTrue(user.has_usable_password())
|
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):
|
def test_create_without_password(self):
|
||||||
Command().handle()
|
Command().handle()
|
||||||
|
|
||||||
self.assertEqual(1, User.objects.count())
|
self.assertEqual(1, User.objects.count())
|
||||||
|
|
||||||
user = User.objects.first()
|
user = User.objects.first()
|
||||||
self.assertEqual('john', user.username)
|
self.assertEqual("john", user.username)
|
||||||
self.assertFalse(user.has_usable_password())
|
self.assertFalse(user.has_usable_password())
|
||||||
|
|
||||||
def test_create_without_options(self):
|
def test_create_without_options(self):
|
||||||
|
@ -35,11 +38,13 @@ class TestCreateInitialSuperuserCommand(TestCase):
|
||||||
|
|
||||||
self.assertEqual(0, User.objects.count())
|
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):
|
def test_create_multiple_times(self):
|
||||||
Command().handle()
|
Command().handle()
|
||||||
Command().handle()
|
Command().handle()
|
||||||
Command().handle()
|
Command().handle()
|
||||||
|
|
||||||
self.assertEqual(1, User.objects.count())
|
self.assertEqual(1, User.objects.count())
|
||||||
|
|
||||||
|
|
|
@ -11,60 +11,93 @@ class ExporterTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
timestamp = int(added.timestamp())
|
timestamp = int(added.timestamp())
|
||||||
|
|
||||||
bookmarks = [
|
bookmarks = [
|
||||||
self.setup_bookmark(url='https://example.com/1', title='Title 1', added=added,
|
self.setup_bookmark(
|
||||||
description='Example description'),
|
url="https://example.com/1",
|
||||||
self.setup_bookmark(url='https://example.com/2', title='Title 2', added=added,
|
title="Title 1",
|
||||||
tags=[self.setup_tag(name='tag1'), self.setup_tag(name='tag2'),
|
added=added,
|
||||||
self.setup_tag(name='tag3')]),
|
description="Example description",
|
||||||
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(
|
||||||
self.setup_bookmark(url='https://example.com/5', title='Title 5', added=added, shared=True,
|
url="https://example.com/2",
|
||||||
description='Example description', notes='Example notes'),
|
title="Title 2",
|
||||||
self.setup_bookmark(url='https://example.com/6', title='Title 6', added=added, shared=True,
|
added=added,
|
||||||
notes='Example notes'),
|
tags=[
|
||||||
self.setup_bookmark(url='https://example.com/7', title='Title 7', added=added, is_archived=True),
|
self.setup_tag(name="tag1"),
|
||||||
self.setup_bookmark(url='https://example.com/8', title='Title 8', added=added,
|
self.setup_tag(name="tag2"),
|
||||||
tags=[self.setup_tag(name='tag4'), self.setup_tag(name='tag5')], is_archived=True),
|
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)
|
html = exporter.export_netscape_html(bookmarks)
|
||||||
|
|
||||||
lines = [
|
lines = [
|
||||||
f'<DT><A HREF="https://example.com/1" ADD_DATE="{timestamp}" PRIVATE="1" TOREAD="0" TAGS="">Title 1</A>',
|
f'<DT><A HREF="https://example.com/1" ADD_DATE="{timestamp}" PRIVATE="1" TOREAD="0" TAGS="">Title 1</A>',
|
||||||
'<DD>Example description',
|
"<DD>Example description",
|
||||||
f'<DT><A HREF="https://example.com/2" ADD_DATE="{timestamp}" PRIVATE="1" TOREAD="0" TAGS="tag1,tag2,tag3">Title 2</A>',
|
f'<DT><A HREF="https://example.com/2" ADD_DATE="{timestamp}" PRIVATE="1" TOREAD="0" TAGS="tag1,tag2,tag3">Title 2</A>',
|
||||||
f'<DT><A HREF="https://example.com/3" ADD_DATE="{timestamp}" PRIVATE="1" TOREAD="1" TAGS="">Title 3</A>',
|
f'<DT><A HREF="https://example.com/3" ADD_DATE="{timestamp}" PRIVATE="1" TOREAD="1" TAGS="">Title 3</A>',
|
||||||
f'<DT><A HREF="https://example.com/4" ADD_DATE="{timestamp}" PRIVATE="0" TOREAD="0" TAGS="">Title 4</A>',
|
f'<DT><A HREF="https://example.com/4" ADD_DATE="{timestamp}" PRIVATE="0" TOREAD="0" TAGS="">Title 4</A>',
|
||||||
f'<DT><A HREF="https://example.com/5" ADD_DATE="{timestamp}" PRIVATE="0" TOREAD="0" TAGS="">Title 5</A>',
|
f'<DT><A HREF="https://example.com/5" ADD_DATE="{timestamp}" PRIVATE="0" TOREAD="0" TAGS="">Title 5</A>',
|
||||||
'<DD>Example description[linkding-notes]Example notes[/linkding-notes]',
|
"<DD>Example description[linkding-notes]Example notes[/linkding-notes]",
|
||||||
f'<DT><A HREF="https://example.com/6" ADD_DATE="{timestamp}" PRIVATE="0" TOREAD="0" TAGS="">Title 6</A>',
|
f'<DT><A HREF="https://example.com/6" ADD_DATE="{timestamp}" PRIVATE="0" TOREAD="0" TAGS="">Title 6</A>',
|
||||||
'<DD>[linkding-notes]Example notes[/linkding-notes]',
|
"<DD>[linkding-notes]Example notes[/linkding-notes]",
|
||||||
f'<DT><A HREF="https://example.com/7" ADD_DATE="{timestamp}" PRIVATE="1" TOREAD="0" TAGS="linkding:archived">Title 7</A>',
|
f'<DT><A HREF="https://example.com/7" ADD_DATE="{timestamp}" PRIVATE="1" TOREAD="0" TAGS="linkding:archived">Title 7</A>',
|
||||||
f'<DT><A HREF="https://example.com/8" ADD_DATE="{timestamp}" PRIVATE="1" TOREAD="0" TAGS="tag4,tag5,linkding:archived">Title 8</A>',
|
f'<DT><A HREF="https://example.com/8" ADD_DATE="{timestamp}" PRIVATE="1" TOREAD="0" TAGS="tag4,tag5,linkding:archived">Title 8</A>',
|
||||||
]
|
]
|
||||||
self.assertIn('\n\r'.join(lines), html)
|
self.assertIn("\n\r".join(lines), html)
|
||||||
|
|
||||||
def test_escape_html(self):
|
def test_escape_html(self):
|
||||||
bookmark = self.setup_bookmark(
|
bookmark = self.setup_bookmark(
|
||||||
title='<style>: The Style Information element',
|
title="<style>: The Style Information element",
|
||||||
description='The <style> HTML element contains style information for a document, or part of a document.',
|
description="The <style> HTML element contains style information for a document, or part of a document.",
|
||||||
notes='Interesting notes about the <style> HTML element.',
|
notes="Interesting notes about the <style> HTML element.",
|
||||||
)
|
)
|
||||||
html = exporter.export_netscape_html([bookmark])
|
html = exporter.export_netscape_html([bookmark])
|
||||||
|
|
||||||
self.assertIn('<style>: The Style Information element', html)
|
self.assertIn("<style>: The Style Information element", html)
|
||||||
self.assertIn(
|
self.assertIn(
|
||||||
'The <style> HTML element contains style information for a document, or part of a document.',
|
"The <style> HTML element contains style information for a document, or part of a document.",
|
||||||
html
|
html,
|
||||||
)
|
|
||||||
self.assertIn(
|
|
||||||
'Interesting notes about the <style> HTML element.',
|
|
||||||
html
|
|
||||||
)
|
)
|
||||||
|
self.assertIn("Interesting notes about the <style> HTML element.", html)
|
||||||
|
|
||||||
def test_handle_empty_values(self):
|
def test_handle_empty_values(self):
|
||||||
bookmark = self.setup_bookmark()
|
bookmark = self.setup_bookmark()
|
||||||
bookmark.title = ''
|
bookmark.title = ""
|
||||||
bookmark.description = ''
|
bookmark.description = ""
|
||||||
bookmark.website_title = None
|
bookmark.website_title = None
|
||||||
bookmark.website_description = None
|
bookmark.website_description = None
|
||||||
bookmark.save()
|
bookmark.save()
|
||||||
|
|
|
@ -25,7 +25,7 @@ class ExporterPerformanceTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
# capture number of queries
|
# capture number of queries
|
||||||
context = CaptureQueriesContext(self.get_connection())
|
context = CaptureQueriesContext(self.get_connection())
|
||||||
with context:
|
with context:
|
||||||
self.client.get(reverse('bookmarks:settings.export'),follow=True)
|
self.client.get(reverse("bookmarks:settings.export"), follow=True)
|
||||||
|
|
||||||
number_of_queries = context.final_queries
|
number_of_queries = context.final_queries
|
||||||
|
|
||||||
|
|
|
@ -9,13 +9,13 @@ from django.test import TestCase, override_settings
|
||||||
|
|
||||||
from bookmarks.services import favicon_loader
|
from bookmarks.services import favicon_loader
|
||||||
|
|
||||||
mock_icon_data = b'mock_icon'
|
mock_icon_data = b"mock_icon"
|
||||||
|
|
||||||
|
|
||||||
class MockStreamingResponse:
|
class MockStreamingResponse:
|
||||||
def __init__(self, data=mock_icon_data, content_type='image/png'):
|
def __init__(self, data=mock_icon_data, content_type="image/png"):
|
||||||
self.chunks = [data]
|
self.chunks = [data]
|
||||||
self.headers = {'Content-Type': content_type}
|
self.headers = {"Content-Type": content_type}
|
||||||
|
|
||||||
def iter_content(self, **kwargs):
|
def iter_content(self, **kwargs):
|
||||||
return self.chunks
|
return self.chunks
|
||||||
|
@ -32,7 +32,7 @@ class FaviconLoaderTestCase(TestCase):
|
||||||
self.ensure_favicon_folder()
|
self.ensure_favicon_folder()
|
||||||
self.clear_favicon_folder()
|
self.clear_favicon_folder()
|
||||||
|
|
||||||
def create_mock_response(self, icon_data=mock_icon_data, content_type='image/png'):
|
def create_mock_response(self, icon_data=mock_icon_data, content_type="image/png"):
|
||||||
mock_response = mock.Mock()
|
mock_response = mock.Mock()
|
||||||
mock_response.raw = io.BytesIO(icon_data)
|
mock_response.raw = io.BytesIO(icon_data)
|
||||||
return MockStreamingResponse(icon_data, content_type)
|
return MockStreamingResponse(icon_data, content_type)
|
||||||
|
@ -59,18 +59,20 @@ class FaviconLoaderTestCase(TestCase):
|
||||||
return len(files)
|
return len(files)
|
||||||
|
|
||||||
def test_load_favicon(self):
|
def test_load_favicon(self):
|
||||||
with mock.patch('requests.get') as mock_get:
|
with mock.patch("requests.get") as mock_get:
|
||||||
mock_get.return_value = self.create_mock_response()
|
mock_get.return_value = self.create_mock_response()
|
||||||
favicon_loader.load_favicon('https://example.com')
|
favicon_loader.load_favicon("https://example.com")
|
||||||
|
|
||||||
# should create icon file
|
# should create icon file
|
||||||
self.assertTrue(self.icon_exists('https_example_com.png'))
|
self.assertTrue(self.icon_exists("https_example_com.png"))
|
||||||
|
|
||||||
# should store image data
|
# should store image data
|
||||||
self.assertEqual(mock_icon_data, self.get_icon_data('https_example_com.png'))
|
self.assertEqual(
|
||||||
|
mock_icon_data, self.get_icon_data("https_example_com.png")
|
||||||
|
)
|
||||||
|
|
||||||
def test_load_favicon_creates_folder_if_not_exists(self):
|
def test_load_favicon_creates_folder_if_not_exists(self):
|
||||||
with mock.patch('requests.get') as mock_get:
|
with mock.patch("requests.get") as mock_get:
|
||||||
mock_get.return_value = self.create_mock_response()
|
mock_get.return_value = self.create_mock_response()
|
||||||
|
|
||||||
folder = Path(settings.LD_FAVICON_FOLDER)
|
folder = Path(settings.LD_FAVICON_FOLDER)
|
||||||
|
@ -78,99 +80,109 @@ class FaviconLoaderTestCase(TestCase):
|
||||||
|
|
||||||
self.assertFalse(folder.exists())
|
self.assertFalse(folder.exists())
|
||||||
|
|
||||||
favicon_loader.load_favicon('https://example.com')
|
favicon_loader.load_favicon("https://example.com")
|
||||||
|
|
||||||
self.assertTrue(folder.exists())
|
self.assertTrue(folder.exists())
|
||||||
|
|
||||||
def test_load_favicon_creates_single_icon_for_same_base_url(self):
|
def test_load_favicon_creates_single_icon_for_same_base_url(self):
|
||||||
with mock.patch('requests.get') as mock_get:
|
with mock.patch("requests.get") as mock_get:
|
||||||
mock_get.return_value = self.create_mock_response()
|
mock_get.return_value = self.create_mock_response()
|
||||||
favicon_loader.load_favicon('https://example.com')
|
favicon_loader.load_favicon("https://example.com")
|
||||||
favicon_loader.load_favicon('https://example.com?foo=bar')
|
favicon_loader.load_favicon("https://example.com?foo=bar")
|
||||||
favicon_loader.load_favicon('https://example.com/foo')
|
favicon_loader.load_favicon("https://example.com/foo")
|
||||||
|
|
||||||
self.assertEqual(1, self.count_icons())
|
self.assertEqual(1, self.count_icons())
|
||||||
self.assertTrue(self.icon_exists('https_example_com.png'))
|
self.assertTrue(self.icon_exists("https_example_com.png"))
|
||||||
|
|
||||||
def test_load_favicon_creates_multiple_icons_for_different_base_url(self):
|
def test_load_favicon_creates_multiple_icons_for_different_base_url(self):
|
||||||
with mock.patch('requests.get') as mock_get:
|
with mock.patch("requests.get") as mock_get:
|
||||||
mock_get.return_value = self.create_mock_response()
|
mock_get.return_value = self.create_mock_response()
|
||||||
favicon_loader.load_favicon('https://example.com')
|
favicon_loader.load_favicon("https://example.com")
|
||||||
favicon_loader.load_favicon('https://sub.example.com')
|
favicon_loader.load_favicon("https://sub.example.com")
|
||||||
favicon_loader.load_favicon('https://other-domain.com')
|
favicon_loader.load_favicon("https://other-domain.com")
|
||||||
|
|
||||||
self.assertEqual(3, self.count_icons())
|
self.assertEqual(3, self.count_icons())
|
||||||
self.assertTrue(self.icon_exists('https_example_com.png'))
|
self.assertTrue(self.icon_exists("https_example_com.png"))
|
||||||
self.assertTrue(self.icon_exists('https_sub_example_com.png'))
|
self.assertTrue(self.icon_exists("https_sub_example_com.png"))
|
||||||
self.assertTrue(self.icon_exists('https_other_domain_com.png'))
|
self.assertTrue(self.icon_exists("https_other_domain_com.png"))
|
||||||
|
|
||||||
def test_load_favicon_caches_icons(self):
|
def test_load_favicon_caches_icons(self):
|
||||||
with mock.patch('requests.get') as mock_get:
|
with mock.patch("requests.get") as mock_get:
|
||||||
mock_get.return_value = self.create_mock_response()
|
mock_get.return_value = self.create_mock_response()
|
||||||
|
|
||||||
favicon_file = favicon_loader.load_favicon('https://example.com')
|
favicon_file = favicon_loader.load_favicon("https://example.com")
|
||||||
mock_get.assert_called()
|
mock_get.assert_called()
|
||||||
self.assertEqual(favicon_file, 'https_example_com.png')
|
self.assertEqual(favicon_file, "https_example_com.png")
|
||||||
|
|
||||||
mock_get.reset_mock()
|
mock_get.reset_mock()
|
||||||
updated_favicon_file = favicon_loader.load_favicon('https://example.com')
|
updated_favicon_file = favicon_loader.load_favicon("https://example.com")
|
||||||
mock_get.assert_not_called()
|
mock_get.assert_not_called()
|
||||||
self.assertEqual(favicon_file, updated_favicon_file)
|
self.assertEqual(favicon_file, updated_favicon_file)
|
||||||
|
|
||||||
def test_load_favicon_updates_stale_icon(self):
|
def test_load_favicon_updates_stale_icon(self):
|
||||||
with mock.patch('requests.get') as mock_get:
|
with mock.patch("requests.get") as mock_get:
|
||||||
mock_get.return_value = self.create_mock_response()
|
mock_get.return_value = self.create_mock_response()
|
||||||
favicon_loader.load_favicon('https://example.com')
|
favicon_loader.load_favicon("https://example.com")
|
||||||
|
|
||||||
icon_path = self.get_icon_path('https_example_com.png')
|
icon_path = self.get_icon_path("https_example_com.png")
|
||||||
|
|
||||||
updated_mock_icon_data = b'updated_mock_icon'
|
updated_mock_icon_data = b"updated_mock_icon"
|
||||||
mock_get.return_value = self.create_mock_response(icon_data=updated_mock_icon_data)
|
mock_get.return_value = self.create_mock_response(
|
||||||
|
icon_data=updated_mock_icon_data
|
||||||
|
)
|
||||||
mock_get.reset_mock()
|
mock_get.reset_mock()
|
||||||
|
|
||||||
# change icon modification date so it is not stale yet
|
# change icon modification date so it is not stale yet
|
||||||
nearly_one_day_ago = time.time() - 60 * 60 * 23
|
nearly_one_day_ago = time.time() - 60 * 60 * 23
|
||||||
os.utime(icon_path.absolute(), (nearly_one_day_ago, nearly_one_day_ago))
|
os.utime(icon_path.absolute(), (nearly_one_day_ago, nearly_one_day_ago))
|
||||||
|
|
||||||
favicon_loader.load_favicon('https://example.com')
|
favicon_loader.load_favicon("https://example.com")
|
||||||
mock_get.assert_not_called()
|
mock_get.assert_not_called()
|
||||||
|
|
||||||
# change icon modification date so it is considered stale
|
# change icon modification date so it is considered stale
|
||||||
one_day_ago = time.time() - 60 * 60 * 24
|
one_day_ago = time.time() - 60 * 60 * 24
|
||||||
os.utime(icon_path.absolute(), (one_day_ago, one_day_ago))
|
os.utime(icon_path.absolute(), (one_day_ago, one_day_ago))
|
||||||
|
|
||||||
favicon_loader.load_favicon('https://example.com')
|
favicon_loader.load_favicon("https://example.com")
|
||||||
mock_get.assert_called()
|
mock_get.assert_called()
|
||||||
self.assertEqual(updated_mock_icon_data, self.get_icon_data('https_example_com.png'))
|
self.assertEqual(
|
||||||
|
updated_mock_icon_data, self.get_icon_data("https_example_com.png")
|
||||||
|
)
|
||||||
|
|
||||||
@override_settings(LD_FAVICON_PROVIDER='https://custom.icons.com/?url={url}')
|
@override_settings(LD_FAVICON_PROVIDER="https://custom.icons.com/?url={url}")
|
||||||
def test_custom_provider_with_url_param(self):
|
def test_custom_provider_with_url_param(self):
|
||||||
with mock.patch('requests.get') as mock_get:
|
with mock.patch("requests.get") as mock_get:
|
||||||
mock_get.return_value = self.create_mock_response()
|
mock_get.return_value = self.create_mock_response()
|
||||||
|
|
||||||
favicon_loader.load_favicon('https://example.com/foo?bar=baz')
|
favicon_loader.load_favicon("https://example.com/foo?bar=baz")
|
||||||
mock_get.assert_called_with('https://custom.icons.com/?url=https://example.com', stream=True)
|
mock_get.assert_called_with(
|
||||||
|
"https://custom.icons.com/?url=https://example.com", stream=True
|
||||||
|
)
|
||||||
|
|
||||||
@override_settings(LD_FAVICON_PROVIDER='https://custom.icons.com/?url={domain}')
|
@override_settings(LD_FAVICON_PROVIDER="https://custom.icons.com/?url={domain}")
|
||||||
def test_custom_provider_with_domain_param(self):
|
def test_custom_provider_with_domain_param(self):
|
||||||
with mock.patch('requests.get') as mock_get:
|
with mock.patch("requests.get") as mock_get:
|
||||||
mock_get.return_value = self.create_mock_response()
|
mock_get.return_value = self.create_mock_response()
|
||||||
|
|
||||||
favicon_loader.load_favicon('https://example.com/foo?bar=baz')
|
favicon_loader.load_favicon("https://example.com/foo?bar=baz")
|
||||||
mock_get.assert_called_with('https://custom.icons.com/?url=example.com', stream=True)
|
mock_get.assert_called_with(
|
||||||
|
"https://custom.icons.com/?url=example.com", stream=True
|
||||||
|
)
|
||||||
|
|
||||||
def test_guess_file_extension(self):
|
def test_guess_file_extension(self):
|
||||||
with mock.patch('requests.get') as mock_get:
|
with mock.patch("requests.get") as mock_get:
|
||||||
mock_get.return_value = self.create_mock_response(content_type='image/png')
|
mock_get.return_value = self.create_mock_response(content_type="image/png")
|
||||||
favicon_loader.load_favicon('https://example.com')
|
favicon_loader.load_favicon("https://example.com")
|
||||||
|
|
||||||
self.assertTrue(self.icon_exists('https_example_com.png'))
|
self.assertTrue(self.icon_exists("https_example_com.png"))
|
||||||
|
|
||||||
self.clear_favicon_folder()
|
self.clear_favicon_folder()
|
||||||
self.ensure_favicon_folder()
|
self.ensure_favicon_folder()
|
||||||
|
|
||||||
with mock.patch('requests.get') as mock_get:
|
with mock.patch("requests.get") as mock_get:
|
||||||
mock_get.return_value = self.create_mock_response(content_type='image/x-icon')
|
mock_get.return_value = self.create_mock_response(
|
||||||
favicon_loader.load_favicon('https://example.com')
|
content_type="image/x-icon"
|
||||||
|
)
|
||||||
|
favicon_loader.load_favicon("https://example.com")
|
||||||
|
|
||||||
self.assertTrue(self.icon_exists('https_example_com.ico'))
|
self.assertTrue(self.icon_exists("https_example_com.ico"))
|
||||||
|
|
|
@ -10,7 +10,6 @@ from bookmarks.models import FeedToken, User
|
||||||
from bookmarks.feeds import sanitize
|
from bookmarks.feeds import sanitize
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def rfc2822_date(date):
|
def rfc2822_date(date):
|
||||||
if not isinstance(date, datetime.datetime):
|
if not isinstance(date, datetime.datetime):
|
||||||
date = datetime.datetime.combine(date, datetime.time())
|
date = datetime.datetime.combine(date, datetime.time())
|
||||||
|
@ -25,43 +24,49 @@ class FeedsTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
self.token = FeedToken.objects.get_or_create(user=user)[0]
|
self.token = FeedToken.objects.get_or_create(user=user)[0]
|
||||||
|
|
||||||
def test_all_returns_404_for_unknown_feed_token(self):
|
def test_all_returns_404_for_unknown_feed_token(self):
|
||||||
response = self.client.get(reverse('bookmarks:feeds.all', args=['foo']))
|
response = self.client.get(reverse("bookmarks:feeds.all", args=["foo"]))
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 404)
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
def test_all_metadata(self):
|
def test_all_metadata(self):
|
||||||
feed_url = reverse('bookmarks:feeds.all', args=[self.token.key])
|
feed_url = reverse("bookmarks:feeds.all", args=[self.token.key])
|
||||||
response = self.client.get(feed_url)
|
response = self.client.get(feed_url)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
self.assertContains(response, '<title>All bookmarks</title>')
|
self.assertContains(response, "<title>All bookmarks</title>")
|
||||||
self.assertContains(response, '<description>All bookmarks</description>')
|
self.assertContains(response, "<description>All bookmarks</description>")
|
||||||
self.assertContains(response, f'<link>http://testserver{feed_url}</link>')
|
self.assertContains(response, f"<link>http://testserver{feed_url}</link>")
|
||||||
self.assertContains(response, f'<atom:link href="http://testserver{feed_url}" rel="self"/>')
|
self.assertContains(
|
||||||
|
response, f'<atom:link href="http://testserver{feed_url}" rel="self"/>'
|
||||||
|
)
|
||||||
|
|
||||||
def test_all_returns_all_unarchived_bookmarks(self):
|
def test_all_returns_all_unarchived_bookmarks(self):
|
||||||
bookmarks = [
|
bookmarks = [
|
||||||
self.setup_bookmark(description='test description'),
|
self.setup_bookmark(description="test description"),
|
||||||
self.setup_bookmark(website_description='test website description'),
|
self.setup_bookmark(website_description="test website description"),
|
||||||
self.setup_bookmark(unread=True, description='test description'),
|
self.setup_bookmark(unread=True, description="test description"),
|
||||||
]
|
]
|
||||||
self.setup_bookmark(is_archived=True)
|
self.setup_bookmark(is_archived=True)
|
||||||
self.setup_bookmark(is_archived=True)
|
self.setup_bookmark(is_archived=True)
|
||||||
self.setup_bookmark(is_archived=True)
|
self.setup_bookmark(is_archived=True)
|
||||||
|
|
||||||
response = self.client.get(reverse('bookmarks:feeds.all', args=[self.token.key]))
|
response = self.client.get(
|
||||||
|
reverse("bookmarks:feeds.all", args=[self.token.key])
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
self.assertContains(response, '<item>', count=len(bookmarks))
|
self.assertContains(response, "<item>", count=len(bookmarks))
|
||||||
|
|
||||||
for bookmark in bookmarks:
|
for bookmark in bookmarks:
|
||||||
expected_item = '<item>' \
|
expected_item = (
|
||||||
f'<title>{bookmark.resolved_title}</title>' \
|
"<item>"
|
||||||
f'<link>{bookmark.url}</link>' \
|
f"<title>{bookmark.resolved_title}</title>"
|
||||||
f'<description>{bookmark.resolved_description}</description>' \
|
f"<link>{bookmark.url}</link>"
|
||||||
f'<pubDate>{rfc2822_date(bookmark.date_added)}</pubDate>' \
|
f"<description>{bookmark.resolved_description}</description>"
|
||||||
f'<guid>{bookmark.url}</guid>' \
|
f"<pubDate>{rfc2822_date(bookmark.date_added)}</pubDate>"
|
||||||
'</item>'
|
f"<guid>{bookmark.url}</guid>"
|
||||||
|
"</item>"
|
||||||
|
)
|
||||||
self.assertContains(response, expected_item, count=1)
|
self.assertContains(response, expected_item, count=1)
|
||||||
|
|
||||||
def test_all_with_query(self):
|
def test_all_with_query(self):
|
||||||
|
@ -74,63 +79,75 @@ class FeedsTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
self.setup_bookmark()
|
self.setup_bookmark()
|
||||||
self.setup_bookmark()
|
self.setup_bookmark()
|
||||||
|
|
||||||
feed_url = reverse('bookmarks:feeds.all', args=[self.token.key])
|
feed_url = reverse("bookmarks:feeds.all", args=[self.token.key])
|
||||||
|
|
||||||
url = feed_url + f'?q={bookmark1.title}'
|
url = feed_url + f"?q={bookmark1.title}"
|
||||||
response = self.client.get(url)
|
response = self.client.get(url)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertContains(response, '<item>', count=1)
|
self.assertContains(response, "<item>", count=1)
|
||||||
self.assertContains(response, f'<guid>{bookmark1.url}</guid>', count=1)
|
self.assertContains(response, f"<guid>{bookmark1.url}</guid>", count=1)
|
||||||
|
|
||||||
url = feed_url + '?q=' + urllib.parse.quote('#' + tag1.name)
|
url = feed_url + "?q=" + urllib.parse.quote("#" + tag1.name)
|
||||||
response = self.client.get(url)
|
response = self.client.get(url)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertContains(response, '<item>', count=2)
|
self.assertContains(response, "<item>", count=2)
|
||||||
self.assertContains(response, f'<guid>{bookmark2.url}</guid>', count=1)
|
self.assertContains(response, f"<guid>{bookmark2.url}</guid>", count=1)
|
||||||
self.assertContains(response, f'<guid>{bookmark3.url}</guid>', count=1)
|
self.assertContains(response, f"<guid>{bookmark3.url}</guid>", count=1)
|
||||||
|
|
||||||
url = feed_url + '?q=' + urllib.parse.quote(f'#{tag1.name} {bookmark2.title}')
|
url = feed_url + "?q=" + urllib.parse.quote(f"#{tag1.name} {bookmark2.title}")
|
||||||
response = self.client.get(url)
|
response = self.client.get(url)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertContains(response, '<item>', count=1)
|
self.assertContains(response, "<item>", count=1)
|
||||||
self.assertContains(response, f'<guid>{bookmark2.url}</guid>', count=1)
|
self.assertContains(response, f"<guid>{bookmark2.url}</guid>", count=1)
|
||||||
|
|
||||||
def test_all_returns_only_user_owned_bookmarks(self):
|
def test_all_returns_only_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"
|
||||||
|
)
|
||||||
self.setup_bookmark(unread=True, user=other_user)
|
self.setup_bookmark(unread=True, user=other_user)
|
||||||
self.setup_bookmark(unread=True, user=other_user)
|
self.setup_bookmark(unread=True, user=other_user)
|
||||||
self.setup_bookmark(unread=True, user=other_user)
|
self.setup_bookmark(unread=True, user=other_user)
|
||||||
|
|
||||||
response = self.client.get(reverse('bookmarks:feeds.all', args=[self.token.key]))
|
response = self.client.get(
|
||||||
|
reverse("bookmarks:feeds.all", args=[self.token.key])
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
self.assertContains(response, '<item>', count=0)
|
self.assertContains(response, "<item>", count=0)
|
||||||
|
|
||||||
def test_strip_control_characters(self):
|
def test_strip_control_characters(self):
|
||||||
self.setup_bookmark(title='test\n\r\t\0\x08title', description='test\n\r\t\0\x08description')
|
self.setup_bookmark(
|
||||||
response = self.client.get(reverse('bookmarks:feeds.all', args=[self.token.key]))
|
title="test\n\r\t\0\x08title", description="test\n\r\t\0\x08description"
|
||||||
|
)
|
||||||
|
response = self.client.get(
|
||||||
|
reverse("bookmarks:feeds.all", args=[self.token.key])
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertContains(response, '<item>', count=1)
|
self.assertContains(response, "<item>", count=1)
|
||||||
self.assertContains(response, f'<title>test\n\r\ttitle</title>', count=1)
|
self.assertContains(response, f"<title>test\n\r\ttitle</title>", count=1)
|
||||||
self.assertContains(response, f'<description>test\n\r\tdescription</description>', count=1)
|
self.assertContains(
|
||||||
|
response, f"<description>test\n\r\tdescription</description>", count=1
|
||||||
|
)
|
||||||
|
|
||||||
def test_sanitize_with_none_text(self):
|
def test_sanitize_with_none_text(self):
|
||||||
self.assertEqual('', sanitize(None))
|
self.assertEqual("", sanitize(None))
|
||||||
|
|
||||||
def test_unread_returns_404_for_unknown_feed_token(self):
|
def test_unread_returns_404_for_unknown_feed_token(self):
|
||||||
response = self.client.get(reverse('bookmarks:feeds.unread', args=['foo']))
|
response = self.client.get(reverse("bookmarks:feeds.unread", args=["foo"]))
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 404)
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
def test_unread_metadata(self):
|
def test_unread_metadata(self):
|
||||||
feed_url = reverse('bookmarks:feeds.unread', args=[self.token.key])
|
feed_url = reverse("bookmarks:feeds.unread", args=[self.token.key])
|
||||||
response = self.client.get(feed_url)
|
response = self.client.get(feed_url)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
self.assertContains(response, '<title>Unread bookmarks</title>')
|
self.assertContains(response, "<title>Unread bookmarks</title>")
|
||||||
self.assertContains(response, '<description>All unread bookmarks</description>')
|
self.assertContains(response, "<description>All unread bookmarks</description>")
|
||||||
self.assertContains(response, f'<link>http://testserver{feed_url}</link>')
|
self.assertContains(response, f"<link>http://testserver{feed_url}</link>")
|
||||||
self.assertContains(response, f'<atom:link href="http://testserver{feed_url}" rel="self"/>')
|
self.assertContains(
|
||||||
|
response, f'<atom:link href="http://testserver{feed_url}" rel="self"/>'
|
||||||
|
)
|
||||||
|
|
||||||
def test_unread_returns_unread_and_unarchived_bookmarks(self):
|
def test_unread_returns_unread_and_unarchived_bookmarks(self):
|
||||||
self.setup_bookmark(unread=False)
|
self.setup_bookmark(unread=False)
|
||||||
|
@ -141,24 +158,30 @@ class FeedsTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
self.setup_bookmark(unread=False, is_archived=True)
|
self.setup_bookmark(unread=False, is_archived=True)
|
||||||
|
|
||||||
unread_bookmarks = [
|
unread_bookmarks = [
|
||||||
self.setup_bookmark(unread=True, description='test description'),
|
self.setup_bookmark(unread=True, description="test description"),
|
||||||
self.setup_bookmark(unread=True, website_description='test website description'),
|
self.setup_bookmark(
|
||||||
self.setup_bookmark(unread=True, description='test description'),
|
unread=True, website_description="test website description"
|
||||||
|
),
|
||||||
|
self.setup_bookmark(unread=True, description="test description"),
|
||||||
]
|
]
|
||||||
|
|
||||||
response = self.client.get(reverse('bookmarks:feeds.unread', args=[self.token.key]))
|
response = self.client.get(
|
||||||
|
reverse("bookmarks:feeds.unread", args=[self.token.key])
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
self.assertContains(response, '<item>', count=len(unread_bookmarks))
|
self.assertContains(response, "<item>", count=len(unread_bookmarks))
|
||||||
|
|
||||||
for bookmark in unread_bookmarks:
|
for bookmark in unread_bookmarks:
|
||||||
expected_item = '<item>' \
|
expected_item = (
|
||||||
f'<title>{bookmark.resolved_title}</title>' \
|
"<item>"
|
||||||
f'<link>{bookmark.url}</link>' \
|
f"<title>{bookmark.resolved_title}</title>"
|
||||||
f'<description>{bookmark.resolved_description}</description>' \
|
f"<link>{bookmark.url}</link>"
|
||||||
f'<pubDate>{rfc2822_date(bookmark.date_added)}</pubDate>' \
|
f"<description>{bookmark.resolved_description}</description>"
|
||||||
f'<guid>{bookmark.url}</guid>' \
|
f"<pubDate>{rfc2822_date(bookmark.date_added)}</pubDate>"
|
||||||
'</item>'
|
f"<guid>{bookmark.url}</guid>"
|
||||||
|
"</item>"
|
||||||
|
)
|
||||||
self.assertContains(response, expected_item, count=1)
|
self.assertContains(response, expected_item, count=1)
|
||||||
|
|
||||||
def test_unread_with_query(self):
|
def test_unread_with_query(self):
|
||||||
|
@ -171,34 +194,38 @@ class FeedsTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
self.setup_bookmark(unread=True)
|
self.setup_bookmark(unread=True)
|
||||||
self.setup_bookmark(unread=True)
|
self.setup_bookmark(unread=True)
|
||||||
|
|
||||||
feed_url = reverse('bookmarks:feeds.all', args=[self.token.key])
|
feed_url = reverse("bookmarks:feeds.all", args=[self.token.key])
|
||||||
|
|
||||||
url = feed_url + f'?q={bookmark1.title}'
|
url = feed_url + f"?q={bookmark1.title}"
|
||||||
response = self.client.get(url)
|
response = self.client.get(url)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertContains(response, '<item>', count=1)
|
self.assertContains(response, "<item>", count=1)
|
||||||
self.assertContains(response, f'<guid>{bookmark1.url}</guid>', count=1)
|
self.assertContains(response, f"<guid>{bookmark1.url}</guid>", count=1)
|
||||||
|
|
||||||
url = feed_url + '?q=' + urllib.parse.quote('#' + tag1.name)
|
url = feed_url + "?q=" + urllib.parse.quote("#" + tag1.name)
|
||||||
response = self.client.get(url)
|
response = self.client.get(url)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertContains(response, '<item>', count=2)
|
self.assertContains(response, "<item>", count=2)
|
||||||
self.assertContains(response, f'<guid>{bookmark2.url}</guid>', count=1)
|
self.assertContains(response, f"<guid>{bookmark2.url}</guid>", count=1)
|
||||||
self.assertContains(response, f'<guid>{bookmark3.url}</guid>', count=1)
|
self.assertContains(response, f"<guid>{bookmark3.url}</guid>", count=1)
|
||||||
|
|
||||||
url = feed_url + '?q=' + urllib.parse.quote(f'#{tag1.name} {bookmark2.title}')
|
url = feed_url + "?q=" + urllib.parse.quote(f"#{tag1.name} {bookmark2.title}")
|
||||||
response = self.client.get(url)
|
response = self.client.get(url)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertContains(response, '<item>', count=1)
|
self.assertContains(response, "<item>", count=1)
|
||||||
self.assertContains(response, f'<guid>{bookmark2.url}</guid>', count=1)
|
self.assertContains(response, f"<guid>{bookmark2.url}</guid>", count=1)
|
||||||
|
|
||||||
def test_unread_returns_only_user_owned_bookmarks(self):
|
def test_unread_returns_only_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"
|
||||||
|
)
|
||||||
self.setup_bookmark(unread=True, user=other_user)
|
self.setup_bookmark(unread=True, user=other_user)
|
||||||
self.setup_bookmark(unread=True, user=other_user)
|
self.setup_bookmark(unread=True, user=other_user)
|
||||||
self.setup_bookmark(unread=True, user=other_user)
|
self.setup_bookmark(unread=True, user=other_user)
|
||||||
|
|
||||||
response = self.client.get(reverse('bookmarks:feeds.unread', args=[self.token.key]))
|
response = self.client.get(
|
||||||
|
reverse("bookmarks:feeds.unread", args=[self.token.key])
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
self.assertContains(response, '<item>', count=0)
|
self.assertContains(response, "<item>", count=0)
|
||||||
|
|
|
@ -27,7 +27,7 @@ class FeedsPerformanceTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
# capture number of queries
|
# capture number of queries
|
||||||
context = CaptureQueriesContext(self.get_connection())
|
context = CaptureQueriesContext(self.get_connection())
|
||||||
with context:
|
with context:
|
||||||
feed_url = reverse('bookmarks:feeds.all', args=[self.token.key])
|
feed_url = reverse("bookmarks:feeds.all", args=[self.token.key])
|
||||||
self.client.get(feed_url)
|
self.client.get(feed_url)
|
||||||
|
|
||||||
number_of_queries = context.final_queries
|
number_of_queries = context.final_queries
|
||||||
|
|
|
@ -14,23 +14,19 @@ class HealthViewTestCase(TestCase):
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
response_body = response.json()
|
response_body = response.json()
|
||||||
expected_body = {
|
expected_body = {"version": app_version, "status": "healthy"}
|
||||||
'version': app_version,
|
|
||||||
'status': 'healthy'
|
|
||||||
}
|
|
||||||
self.assertDictEqual(response_body, expected_body)
|
self.assertDictEqual(response_body, expected_body)
|
||||||
|
|
||||||
def test_health_unhealhty(self):
|
def test_health_unhealhty(self):
|
||||||
with patch.object(connections['default'], 'ensure_connection') as mock_ensure_connection:
|
with patch.object(
|
||||||
mock_ensure_connection.side_effect = Exception('Connection error')
|
connections["default"], "ensure_connection"
|
||||||
|
) as mock_ensure_connection:
|
||||||
|
mock_ensure_connection.side_effect = Exception("Connection error")
|
||||||
|
|
||||||
response = self.client.get("/health")
|
response = self.client.get("/health")
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 500)
|
self.assertEqual(response.status_code, 500)
|
||||||
|
|
||||||
response_body = response.json()
|
response_body = response.json()
|
||||||
expected_body = {
|
expected_body = {"version": app_version, "status": "unhealthy"}
|
||||||
'version': app_version,
|
|
||||||
'status': 'unhealthy'
|
|
||||||
}
|
|
||||||
self.assertDictEqual(response_body, expected_body)
|
self.assertDictEqual(response_body, expected_body)
|
||||||
|
|
|
@ -7,7 +7,12 @@ from django.utils import timezone
|
||||||
from bookmarks.models import Bookmark, Tag, parse_tag_string
|
from bookmarks.models import Bookmark, Tag, parse_tag_string
|
||||||
from bookmarks.services import tasks
|
from bookmarks.services import tasks
|
||||||
from bookmarks.services.importer import import_netscape_html, ImportOptions
|
from bookmarks.services.importer import import_netscape_html, ImportOptions
|
||||||
from bookmarks.tests.helpers import BookmarkFactoryMixin, ImportTestMixin, BookmarkHtmlTag, disable_logging
|
from bookmarks.tests.helpers import (
|
||||||
|
BookmarkFactoryMixin,
|
||||||
|
ImportTestMixin,
|
||||||
|
BookmarkHtmlTag,
|
||||||
|
disable_logging,
|
||||||
|
)
|
||||||
from bookmarks.utils import parse_timestamp
|
from bookmarks.utils import parse_timestamp
|
||||||
|
|
||||||
|
|
||||||
|
@ -29,19 +34,40 @@ class ImporterTestCase(TestCase, BookmarkFactoryMixin, ImportTestMixin):
|
||||||
# Check assigned tags
|
# Check assigned tags
|
||||||
for tag_name in tag_names:
|
for tag_name in tag_names:
|
||||||
tag = next(
|
tag = next(
|
||||||
(tag for tag in bookmark.tags.all() if tag.name == tag_name), None)
|
(tag for tag in bookmark.tags.all() if tag.name == tag_name), None
|
||||||
|
)
|
||||||
self.assertIsNotNone(tag)
|
self.assertIsNotNone(tag)
|
||||||
|
|
||||||
def test_import(self):
|
def test_import(self):
|
||||||
html_tags = [
|
html_tags = [
|
||||||
BookmarkHtmlTag(href='https://example.com', title='Example title', description='Example description',
|
BookmarkHtmlTag(
|
||||||
add_date='1', tags='example-tag'),
|
href="https://example.com",
|
||||||
BookmarkHtmlTag(href='https://example.com/foo', title='Foo title', description='',
|
title="Example title",
|
||||||
add_date='2', tags=''),
|
description="Example description",
|
||||||
BookmarkHtmlTag(href='https://example.com/bar', title='Bar title', description='Bar description',
|
add_date="1",
|
||||||
add_date='3', tags='bar-tag, other-tag'),
|
tags="example-tag",
|
||||||
BookmarkHtmlTag(href='https://example.com/baz', title='Baz title', description='Baz description',
|
),
|
||||||
add_date='4', to_read=True),
|
BookmarkHtmlTag(
|
||||||
|
href="https://example.com/foo",
|
||||||
|
title="Foo title",
|
||||||
|
description="",
|
||||||
|
add_date="2",
|
||||||
|
tags="",
|
||||||
|
),
|
||||||
|
BookmarkHtmlTag(
|
||||||
|
href="https://example.com/bar",
|
||||||
|
title="Bar title",
|
||||||
|
description="Bar description",
|
||||||
|
add_date="3",
|
||||||
|
tags="bar-tag, other-tag",
|
||||||
|
),
|
||||||
|
BookmarkHtmlTag(
|
||||||
|
href="https://example.com/baz",
|
||||||
|
title="Baz title",
|
||||||
|
description="Baz description",
|
||||||
|
add_date="4",
|
||||||
|
to_read=True,
|
||||||
|
),
|
||||||
]
|
]
|
||||||
import_html = self.render_html(tags=html_tags)
|
import_html = self.render_html(tags=html_tags)
|
||||||
result = import_netscape_html(import_html, self.get_or_create_test_user())
|
result = import_netscape_html(import_html, self.get_or_create_test_user())
|
||||||
|
@ -59,17 +85,41 @@ class ImporterTestCase(TestCase, BookmarkFactoryMixin, ImportTestMixin):
|
||||||
def test_synchronize(self):
|
def test_synchronize(self):
|
||||||
# Initial import
|
# Initial import
|
||||||
html_tags = [
|
html_tags = [
|
||||||
BookmarkHtmlTag(href='https://example.com', title='Example title', description='Example description',
|
BookmarkHtmlTag(
|
||||||
add_date='1', tags='example-tag'),
|
href="https://example.com",
|
||||||
BookmarkHtmlTag(href='https://example.com/foo', title='Foo title', description='',
|
title="Example title",
|
||||||
add_date='2', tags=''),
|
description="Example description",
|
||||||
BookmarkHtmlTag(href='https://example.com/bar', title='Bar title', description='Bar description',
|
add_date="1",
|
||||||
add_date='3', tags='bar-tag, other-tag'),
|
tags="example-tag",
|
||||||
BookmarkHtmlTag(href='https://example.com/unread', title='Unread title', description='Unread description',
|
),
|
||||||
add_date='3', to_read=True),
|
BookmarkHtmlTag(
|
||||||
BookmarkHtmlTag(href='https://example.com/private', title='Private title',
|
href="https://example.com/foo",
|
||||||
description='Private description',
|
title="Foo title",
|
||||||
add_date='4', private=True),
|
description="",
|
||||||
|
add_date="2",
|
||||||
|
tags="",
|
||||||
|
),
|
||||||
|
BookmarkHtmlTag(
|
||||||
|
href="https://example.com/bar",
|
||||||
|
title="Bar title",
|
||||||
|
description="Bar description",
|
||||||
|
add_date="3",
|
||||||
|
tags="bar-tag, other-tag",
|
||||||
|
),
|
||||||
|
BookmarkHtmlTag(
|
||||||
|
href="https://example.com/unread",
|
||||||
|
title="Unread title",
|
||||||
|
description="Unread description",
|
||||||
|
add_date="3",
|
||||||
|
to_read=True,
|
||||||
|
),
|
||||||
|
BookmarkHtmlTag(
|
||||||
|
href="https://example.com/private",
|
||||||
|
title="Private title",
|
||||||
|
description="Private description",
|
||||||
|
add_date="4",
|
||||||
|
private=True,
|
||||||
|
),
|
||||||
]
|
]
|
||||||
import_html = self.render_html(tags=html_tags)
|
import_html = self.render_html(tags=html_tags)
|
||||||
import_netscape_html(import_html, self.get_or_create_test_user())
|
import_netscape_html(import_html, self.get_or_create_test_user())
|
||||||
|
@ -81,25 +131,51 @@ class ImporterTestCase(TestCase, BookmarkFactoryMixin, ImportTestMixin):
|
||||||
|
|
||||||
# Change data, add some new data
|
# Change data, add some new data
|
||||||
html_tags = [
|
html_tags = [
|
||||||
BookmarkHtmlTag(href='https://example.com', title='Updated Example title',
|
BookmarkHtmlTag(
|
||||||
description='Updated Example description', add_date='111', tags='updated-example-tag'),
|
href="https://example.com",
|
||||||
BookmarkHtmlTag(href='https://example.com/foo', title='Updated Foo title',
|
title="Updated Example title",
|
||||||
description='Updated Foo description',
|
description="Updated Example description",
|
||||||
add_date='222', tags='new-tag'),
|
add_date="111",
|
||||||
BookmarkHtmlTag(href='https://example.com/bar', title='Updated Bar title',
|
tags="updated-example-tag",
|
||||||
description='Updated Bar description',
|
),
|
||||||
add_date='333', tags='updated-bar-tag, updated-other-tag'),
|
BookmarkHtmlTag(
|
||||||
BookmarkHtmlTag(href='https://example.com/unread', title='Unread title', description='Unread description',
|
href="https://example.com/foo",
|
||||||
add_date='3', to_read=False),
|
title="Updated Foo title",
|
||||||
BookmarkHtmlTag(href='https://example.com/private', title='Private title',
|
description="Updated Foo description",
|
||||||
description='Private description',
|
add_date="222",
|
||||||
add_date='4', private=False),
|
tags="new-tag",
|
||||||
BookmarkHtmlTag(href='https://baz.com', add_date='444', tags='baz-tag')
|
),
|
||||||
|
BookmarkHtmlTag(
|
||||||
|
href="https://example.com/bar",
|
||||||
|
title="Updated Bar title",
|
||||||
|
description="Updated Bar description",
|
||||||
|
add_date="333",
|
||||||
|
tags="updated-bar-tag, updated-other-tag",
|
||||||
|
),
|
||||||
|
BookmarkHtmlTag(
|
||||||
|
href="https://example.com/unread",
|
||||||
|
title="Unread title",
|
||||||
|
description="Unread description",
|
||||||
|
add_date="3",
|
||||||
|
to_read=False,
|
||||||
|
),
|
||||||
|
BookmarkHtmlTag(
|
||||||
|
href="https://example.com/private",
|
||||||
|
title="Private title",
|
||||||
|
description="Private description",
|
||||||
|
add_date="4",
|
||||||
|
private=False,
|
||||||
|
),
|
||||||
|
BookmarkHtmlTag(href="https://baz.com", add_date="444", tags="baz-tag"),
|
||||||
]
|
]
|
||||||
|
|
||||||
# Import updated data
|
# Import updated data
|
||||||
import_html = self.render_html(tags=html_tags)
|
import_html = self.render_html(tags=html_tags)
|
||||||
result = import_netscape_html(import_html, self.get_or_create_test_user(), ImportOptions(map_private_flag=True))
|
result = import_netscape_html(
|
||||||
|
import_html,
|
||||||
|
self.get_or_create_test_user(),
|
||||||
|
ImportOptions(map_private_flag=True),
|
||||||
|
)
|
||||||
|
|
||||||
# Check result
|
# Check result
|
||||||
self.assertEqual(result.total, 6)
|
self.assertEqual(result.total, 6)
|
||||||
|
@ -113,9 +189,9 @@ class ImporterTestCase(TestCase, BookmarkFactoryMixin, ImportTestMixin):
|
||||||
|
|
||||||
def test_import_with_some_invalid_bookmarks(self):
|
def test_import_with_some_invalid_bookmarks(self):
|
||||||
html_tags = [
|
html_tags = [
|
||||||
BookmarkHtmlTag(href='https://example.com'),
|
BookmarkHtmlTag(href="https://example.com"),
|
||||||
# Invalid URL
|
# Invalid URL
|
||||||
BookmarkHtmlTag(href='foo.com'),
|
BookmarkHtmlTag(href="foo.com"),
|
||||||
# No URL
|
# No URL
|
||||||
BookmarkHtmlTag(),
|
BookmarkHtmlTag(),
|
||||||
]
|
]
|
||||||
|
@ -135,21 +211,23 @@ class ImporterTestCase(TestCase, BookmarkFactoryMixin, ImportTestMixin):
|
||||||
def test_import_invalid_bookmark_does_not_associate_tags(self):
|
def test_import_invalid_bookmark_does_not_associate_tags(self):
|
||||||
html_tags = [
|
html_tags = [
|
||||||
# No URL
|
# No URL
|
||||||
BookmarkHtmlTag(tags='tag1, tag2, tag3'),
|
BookmarkHtmlTag(tags="tag1, tag2, tag3"),
|
||||||
]
|
]
|
||||||
import_html = self.render_html(tags=html_tags)
|
import_html = self.render_html(tags=html_tags)
|
||||||
# Sqlite silently ignores relationships that have a non-persisted bookmark,
|
# Sqlite silently ignores relationships that have a non-persisted bookmark,
|
||||||
# thus testing if the bulk create receives no relationships
|
# thus testing if the bulk create receives no relationships
|
||||||
BookmarkToTagRelationShip = Bookmark.tags.through
|
BookmarkToTagRelationShip = Bookmark.tags.through
|
||||||
with patch.object(BookmarkToTagRelationShip.objects, 'bulk_create') as mock_bulk_create:
|
with patch.object(
|
||||||
|
BookmarkToTagRelationShip.objects, "bulk_create"
|
||||||
|
) as mock_bulk_create:
|
||||||
import_netscape_html(import_html, self.get_or_create_test_user())
|
import_netscape_html(import_html, self.get_or_create_test_user())
|
||||||
mock_bulk_create.assert_called_once_with([], ignore_conflicts=True)
|
mock_bulk_create.assert_called_once_with([], ignore_conflicts=True)
|
||||||
|
|
||||||
def test_import_tags(self):
|
def test_import_tags(self):
|
||||||
html_tags = [
|
html_tags = [
|
||||||
BookmarkHtmlTag(href='https://example.com', tags='tag1'),
|
BookmarkHtmlTag(href="https://example.com", tags="tag1"),
|
||||||
BookmarkHtmlTag(href='https://foo.com', tags='tag2'),
|
BookmarkHtmlTag(href="https://foo.com", tags="tag2"),
|
||||||
BookmarkHtmlTag(href='https://bar.com', tags='tag3'),
|
BookmarkHtmlTag(href="https://bar.com", tags="tag3"),
|
||||||
]
|
]
|
||||||
import_html = self.render_html(tags=html_tags)
|
import_html = self.render_html(tags=html_tags)
|
||||||
import_netscape_html(import_html, self.get_or_create_test_user())
|
import_netscape_html(import_html, self.get_or_create_test_user())
|
||||||
|
@ -158,16 +236,14 @@ class ImporterTestCase(TestCase, BookmarkFactoryMixin, ImportTestMixin):
|
||||||
|
|
||||||
def test_create_missing_tags(self):
|
def test_create_missing_tags(self):
|
||||||
html_tags = [
|
html_tags = [
|
||||||
BookmarkHtmlTag(href='https://example.com', tags='tag1'),
|
BookmarkHtmlTag(href="https://example.com", tags="tag1"),
|
||||||
BookmarkHtmlTag(href='https://foo.com', tags='tag2'),
|
BookmarkHtmlTag(href="https://foo.com", tags="tag2"),
|
||||||
BookmarkHtmlTag(href='https://bar.com', tags='tag3'),
|
BookmarkHtmlTag(href="https://bar.com", tags="tag3"),
|
||||||
]
|
]
|
||||||
import_html = self.render_html(tags=html_tags)
|
import_html = self.render_html(tags=html_tags)
|
||||||
import_netscape_html(import_html, self.get_or_create_test_user())
|
import_netscape_html(import_html, self.get_or_create_test_user())
|
||||||
|
|
||||||
html_tags.append(
|
html_tags.append(BookmarkHtmlTag(href="https://baz.com", tags="tag4"))
|
||||||
BookmarkHtmlTag(href='https://baz.com', tags='tag4')
|
|
||||||
)
|
|
||||||
import_html = self.render_html(tags=html_tags)
|
import_html = self.render_html(tags=html_tags)
|
||||||
import_netscape_html(import_html, self.get_or_create_test_user())
|
import_netscape_html(import_html, self.get_or_create_test_user())
|
||||||
|
|
||||||
|
@ -175,9 +251,9 @@ class ImporterTestCase(TestCase, BookmarkFactoryMixin, ImportTestMixin):
|
||||||
|
|
||||||
def test_create_missing_tags_does_not_duplicate_tags(self):
|
def test_create_missing_tags_does_not_duplicate_tags(self):
|
||||||
html_tags = [
|
html_tags = [
|
||||||
BookmarkHtmlTag(href='https://example.com', tags='tag1'),
|
BookmarkHtmlTag(href="https://example.com", tags="tag1"),
|
||||||
BookmarkHtmlTag(href='https://foo.com', tags='tag1'),
|
BookmarkHtmlTag(href="https://foo.com", tags="tag1"),
|
||||||
BookmarkHtmlTag(href='https://bar.com', tags='tag1'),
|
BookmarkHtmlTag(href="https://bar.com", tags="tag1"),
|
||||||
]
|
]
|
||||||
import_html = self.render_html(tags=html_tags)
|
import_html = self.render_html(tags=html_tags)
|
||||||
import_netscape_html(import_html, self.get_or_create_test_user())
|
import_netscape_html(import_html, self.get_or_create_test_user())
|
||||||
|
@ -186,14 +262,12 @@ class ImporterTestCase(TestCase, BookmarkFactoryMixin, ImportTestMixin):
|
||||||
|
|
||||||
def test_should_append_tags_to_bookmark_when_reimporting_with_different_tags(self):
|
def test_should_append_tags_to_bookmark_when_reimporting_with_different_tags(self):
|
||||||
html_tags = [
|
html_tags = [
|
||||||
BookmarkHtmlTag(href='https://example.com', tags='tag1'),
|
BookmarkHtmlTag(href="https://example.com", tags="tag1"),
|
||||||
]
|
]
|
||||||
import_html = self.render_html(tags=html_tags)
|
import_html = self.render_html(tags=html_tags)
|
||||||
import_netscape_html(import_html, self.get_or_create_test_user())
|
import_netscape_html(import_html, self.get_or_create_test_user())
|
||||||
|
|
||||||
html_tags.append(
|
html_tags.append(BookmarkHtmlTag(href="https://example.com", tags="tag2, tag3"))
|
||||||
BookmarkHtmlTag(href='https://example.com', tags='tag2, tag3')
|
|
||||||
)
|
|
||||||
import_html = self.render_html(tags=html_tags)
|
import_html = self.render_html(tags=html_tags)
|
||||||
import_netscape_html(import_html, self.get_or_create_test_user())
|
import_netscape_html(import_html, self.get_or_create_test_user())
|
||||||
|
|
||||||
|
@ -202,65 +276,71 @@ class ImporterTestCase(TestCase, BookmarkFactoryMixin, ImportTestMixin):
|
||||||
|
|
||||||
@override_settings(USE_TZ=False)
|
@override_settings(USE_TZ=False)
|
||||||
def test_use_current_date_when_no_add_date(self):
|
def test_use_current_date_when_no_add_date(self):
|
||||||
test_html = self.render_html(tags_html=f'''
|
test_html = self.render_html(
|
||||||
|
tags_html=f"""
|
||||||
<DT><A HREF="https://example.com">Example.com</A>
|
<DT><A HREF="https://example.com">Example.com</A>
|
||||||
<DD>Example.com
|
<DD>Example.com
|
||||||
''')
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
with patch.object(timezone, 'now', return_value=timezone.datetime(2021, 1, 1)):
|
with patch.object(timezone, "now", return_value=timezone.datetime(2021, 1, 1)):
|
||||||
import_netscape_html(test_html, self.get_or_create_test_user())
|
import_netscape_html(test_html, self.get_or_create_test_user())
|
||||||
|
|
||||||
self.assertEqual(Bookmark.objects.count(), 1)
|
self.assertEqual(Bookmark.objects.count(), 1)
|
||||||
self.assertEqual(Bookmark.objects.all()[0].date_added, timezone.datetime(2021, 1, 1))
|
self.assertEqual(
|
||||||
|
Bookmark.objects.all()[0].date_added, timezone.datetime(2021, 1, 1)
|
||||||
|
)
|
||||||
|
|
||||||
def test_keep_title_if_imported_bookmark_has_empty_title(self):
|
def test_keep_title_if_imported_bookmark_has_empty_title(self):
|
||||||
test_html = self.render_html(tags=[
|
test_html = self.render_html(
|
||||||
BookmarkHtmlTag(href='https://example.com', title='Example.com')
|
tags=[BookmarkHtmlTag(href="https://example.com", title="Example.com")]
|
||||||
])
|
)
|
||||||
import_netscape_html(test_html, self.get_or_create_test_user())
|
import_netscape_html(test_html, self.get_or_create_test_user())
|
||||||
|
|
||||||
test_html = self.render_html(tags=[
|
test_html = self.render_html(tags=[BookmarkHtmlTag(href="https://example.com")])
|
||||||
BookmarkHtmlTag(href='https://example.com')
|
|
||||||
])
|
|
||||||
import_netscape_html(test_html, self.get_or_create_test_user())
|
import_netscape_html(test_html, self.get_or_create_test_user())
|
||||||
|
|
||||||
self.assertEqual(Bookmark.objects.count(), 1)
|
self.assertEqual(Bookmark.objects.count(), 1)
|
||||||
self.assertEqual(Bookmark.objects.all()[0].title, 'Example.com')
|
self.assertEqual(Bookmark.objects.all()[0].title, "Example.com")
|
||||||
|
|
||||||
def test_keep_description_if_imported_bookmark_has_empty_description(self):
|
def test_keep_description_if_imported_bookmark_has_empty_description(self):
|
||||||
test_html = self.render_html(tags=[
|
test_html = self.render_html(
|
||||||
BookmarkHtmlTag(href='https://example.com', description='Example.com')
|
tags=[
|
||||||
])
|
BookmarkHtmlTag(href="https://example.com", description="Example.com")
|
||||||
|
]
|
||||||
|
)
|
||||||
import_netscape_html(test_html, self.get_or_create_test_user())
|
import_netscape_html(test_html, self.get_or_create_test_user())
|
||||||
|
|
||||||
test_html = self.render_html(tags=[
|
test_html = self.render_html(tags=[BookmarkHtmlTag(href="https://example.com")])
|
||||||
BookmarkHtmlTag(href='https://example.com')
|
|
||||||
])
|
|
||||||
import_netscape_html(test_html, self.get_or_create_test_user())
|
import_netscape_html(test_html, self.get_or_create_test_user())
|
||||||
|
|
||||||
self.assertEqual(Bookmark.objects.count(), 1)
|
self.assertEqual(Bookmark.objects.count(), 1)
|
||||||
self.assertEqual(Bookmark.objects.all()[0].description, 'Example.com')
|
self.assertEqual(Bookmark.objects.all()[0].description, "Example.com")
|
||||||
|
|
||||||
def test_replace_whitespace_in_tag_names(self):
|
def test_replace_whitespace_in_tag_names(self):
|
||||||
test_html = self.render_html(tags_html=f'''
|
test_html = self.render_html(
|
||||||
|
tags_html=f"""
|
||||||
<DT><A HREF="https://example.com" TAGS="tag 1, tag 2, tag 3">Example.com</A>
|
<DT><A HREF="https://example.com" TAGS="tag 1, tag 2, tag 3">Example.com</A>
|
||||||
<DD>Example.com
|
<DD>Example.com
|
||||||
''')
|
"""
|
||||||
|
)
|
||||||
import_netscape_html(test_html, self.get_or_create_test_user())
|
import_netscape_html(test_html, self.get_or_create_test_user())
|
||||||
|
|
||||||
tags = Tag.objects.all()
|
tags = Tag.objects.all()
|
||||||
tag_names = [tag.name for tag in tags]
|
tag_names = [tag.name for tag in tags]
|
||||||
|
|
||||||
self.assertListEqual(tag_names, ['tag-1', 'tag-2', 'tag-3'])
|
self.assertListEqual(tag_names, ["tag-1", "tag-2", "tag-3"])
|
||||||
|
|
||||||
@disable_logging
|
@disable_logging
|
||||||
def test_validate_empty_or_missing_bookmark_url(self):
|
def test_validate_empty_or_missing_bookmark_url(self):
|
||||||
test_html = self.render_html(tags_html=f'''
|
test_html = self.render_html(
|
||||||
|
tags_html=f"""
|
||||||
<DT><A HREF="">Empty URL</A>
|
<DT><A HREF="">Empty URL</A>
|
||||||
<DD>Empty URL
|
<DD>Empty URL
|
||||||
<DT><A>Missing URL</A>
|
<DT><A>Missing URL</A>
|
||||||
<DD>Missing URL
|
<DD>Missing URL
|
||||||
''')
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
import_result = import_netscape_html(test_html, self.get_or_create_test_user())
|
import_result = import_netscape_html(test_html, self.get_or_create_test_user())
|
||||||
|
|
||||||
|
@ -270,14 +350,16 @@ class ImporterTestCase(TestCase, BookmarkFactoryMixin, ImportTestMixin):
|
||||||
|
|
||||||
def test_private_flag(self):
|
def test_private_flag(self):
|
||||||
# does not map private flag if not enabled in options
|
# does not map private flag if not enabled in options
|
||||||
test_html = self.render_html(tags_html='''
|
test_html = self.render_html(
|
||||||
|
tags_html="""
|
||||||
<DT><A HREF="https://example.com/1" ADD_DATE="1">Example title 1</A>
|
<DT><A HREF="https://example.com/1" ADD_DATE="1">Example title 1</A>
|
||||||
<DD>Example description 1</DD>
|
<DD>Example description 1</DD>
|
||||||
<DT><A HREF="https://example.com/2" ADD_DATE="1" PRIVATE="1">Example title 2</A>
|
<DT><A HREF="https://example.com/2" ADD_DATE="1" PRIVATE="1">Example title 2</A>
|
||||||
<DD>Example description 2</DD>
|
<DD>Example description 2</DD>
|
||||||
<DT><A HREF="https://example.com/3" ADD_DATE="1" PRIVATE="0">Example title 3</A>
|
<DT><A HREF="https://example.com/3" ADD_DATE="1" PRIVATE="0">Example title 3</A>
|
||||||
<DD>Example description 3</DD>
|
<DD>Example description 3</DD>
|
||||||
''')
|
"""
|
||||||
|
)
|
||||||
import_netscape_html(test_html, self.get_or_create_test_user(), ImportOptions())
|
import_netscape_html(test_html, self.get_or_create_test_user(), ImportOptions())
|
||||||
|
|
||||||
self.assertEqual(Bookmark.objects.count(), 3)
|
self.assertEqual(Bookmark.objects.count(), 3)
|
||||||
|
@ -287,23 +369,29 @@ class ImporterTestCase(TestCase, BookmarkFactoryMixin, ImportTestMixin):
|
||||||
|
|
||||||
# does map private flag if enabled in options
|
# does map private flag if enabled in options
|
||||||
Bookmark.objects.all().delete()
|
Bookmark.objects.all().delete()
|
||||||
import_netscape_html(test_html, self.get_or_create_test_user(), ImportOptions(map_private_flag=True))
|
import_netscape_html(
|
||||||
bookmark1 = Bookmark.objects.get(url='https://example.com/1')
|
test_html,
|
||||||
bookmark2 = Bookmark.objects.get(url='https://example.com/2')
|
self.get_or_create_test_user(),
|
||||||
bookmark3 = Bookmark.objects.get(url='https://example.com/3')
|
ImportOptions(map_private_flag=True),
|
||||||
|
)
|
||||||
|
bookmark1 = Bookmark.objects.get(url="https://example.com/1")
|
||||||
|
bookmark2 = Bookmark.objects.get(url="https://example.com/2")
|
||||||
|
bookmark3 = Bookmark.objects.get(url="https://example.com/3")
|
||||||
self.assertEqual(bookmark1.shared, False)
|
self.assertEqual(bookmark1.shared, False)
|
||||||
self.assertEqual(bookmark2.shared, False)
|
self.assertEqual(bookmark2.shared, False)
|
||||||
self.assertEqual(bookmark3.shared, True)
|
self.assertEqual(bookmark3.shared, True)
|
||||||
|
|
||||||
def test_archived_state(self):
|
def test_archived_state(self):
|
||||||
test_html = self.render_html(tags_html='''
|
test_html = self.render_html(
|
||||||
|
tags_html="""
|
||||||
<DT><A HREF="https://example.com/1" ADD_DATE="1" TAGS="tag1,tag2,linkding:archived">Example title 1</A>
|
<DT><A HREF="https://example.com/1" ADD_DATE="1" TAGS="tag1,tag2,linkding:archived">Example title 1</A>
|
||||||
<DD>Example description 1</DD>
|
<DD>Example description 1</DD>
|
||||||
<DT><A HREF="https://example.com/2" ADD_DATE="1" PRIVATE="1" TAGS="tag1,tag2">Example title 2</A>
|
<DT><A HREF="https://example.com/2" ADD_DATE="1" PRIVATE="1" TAGS="tag1,tag2">Example title 2</A>
|
||||||
<DD>Example description 2</DD>
|
<DD>Example description 2</DD>
|
||||||
<DT><A HREF="https://example.com/3" ADD_DATE="1" PRIVATE="0">Example title 3</A>
|
<DT><A HREF="https://example.com/3" ADD_DATE="1" PRIVATE="0">Example title 3</A>
|
||||||
<DD>Example description 3</DD>
|
<DD>Example description 3</DD>
|
||||||
''')
|
"""
|
||||||
|
)
|
||||||
import_netscape_html(test_html, self.get_or_create_test_user(), ImportOptions())
|
import_netscape_html(test_html, self.get_or_create_test_user(), ImportOptions())
|
||||||
|
|
||||||
self.assertEqual(Bookmark.objects.count(), 3)
|
self.assertEqual(Bookmark.objects.count(), 3)
|
||||||
|
@ -313,57 +401,67 @@ class ImporterTestCase(TestCase, BookmarkFactoryMixin, ImportTestMixin):
|
||||||
|
|
||||||
tags = Tag.objects.all()
|
tags = Tag.objects.all()
|
||||||
self.assertEqual(len(tags), 2)
|
self.assertEqual(len(tags), 2)
|
||||||
self.assertEqual(tags[0].name, 'tag1')
|
self.assertEqual(tags[0].name, "tag1")
|
||||||
self.assertEqual(tags[1].name, 'tag2')
|
self.assertEqual(tags[1].name, "tag2")
|
||||||
|
|
||||||
def test_notes(self):
|
def test_notes(self):
|
||||||
# initial notes
|
# initial notes
|
||||||
test_html = self.render_html(tags_html='''
|
test_html = self.render_html(
|
||||||
|
tags_html="""
|
||||||
<DT><A HREF="https://example.com" ADD_DATE="1">Example title</A>
|
<DT><A HREF="https://example.com" ADD_DATE="1">Example title</A>
|
||||||
<DD>Example description[linkding-notes]Example notes[/linkding-notes]
|
<DD>Example description[linkding-notes]Example notes[/linkding-notes]
|
||||||
''')
|
"""
|
||||||
|
)
|
||||||
import_netscape_html(test_html, self.get_or_create_test_user(), ImportOptions())
|
import_netscape_html(test_html, self.get_or_create_test_user(), ImportOptions())
|
||||||
|
|
||||||
self.assertEqual(Bookmark.objects.count(), 1)
|
self.assertEqual(Bookmark.objects.count(), 1)
|
||||||
self.assertEqual(Bookmark.objects.all()[0].description, 'Example description')
|
self.assertEqual(Bookmark.objects.all()[0].description, "Example description")
|
||||||
self.assertEqual(Bookmark.objects.all()[0].notes, 'Example notes')
|
self.assertEqual(Bookmark.objects.all()[0].notes, "Example notes")
|
||||||
|
|
||||||
# update notes
|
# update notes
|
||||||
test_html = self.render_html(tags_html='''
|
test_html = self.render_html(
|
||||||
|
tags_html="""
|
||||||
<DT><A HREF="https://example.com" ADD_DATE="1">Example title</A>
|
<DT><A HREF="https://example.com" ADD_DATE="1">Example title</A>
|
||||||
<DD>Example description[linkding-notes]Updated notes[/linkding-notes]
|
<DD>Example description[linkding-notes]Updated notes[/linkding-notes]
|
||||||
''')
|
"""
|
||||||
|
)
|
||||||
import_netscape_html(test_html, self.get_or_create_test_user(), ImportOptions())
|
import_netscape_html(test_html, self.get_or_create_test_user(), ImportOptions())
|
||||||
|
|
||||||
self.assertEqual(Bookmark.objects.count(), 1)
|
self.assertEqual(Bookmark.objects.count(), 1)
|
||||||
self.assertEqual(Bookmark.objects.all()[0].description, 'Example description')
|
self.assertEqual(Bookmark.objects.all()[0].description, "Example description")
|
||||||
self.assertEqual(Bookmark.objects.all()[0].notes, 'Updated notes')
|
self.assertEqual(Bookmark.objects.all()[0].notes, "Updated notes")
|
||||||
|
|
||||||
# does not override existing notes if empty
|
# does not override existing notes if empty
|
||||||
test_html = self.render_html(tags_html='''
|
test_html = self.render_html(
|
||||||
|
tags_html="""
|
||||||
<DT><A HREF="https://example.com" ADD_DATE="1">Example title</A>
|
<DT><A HREF="https://example.com" ADD_DATE="1">Example title</A>
|
||||||
<DD>Example description
|
<DD>Example description
|
||||||
''')
|
"""
|
||||||
|
)
|
||||||
import_netscape_html(test_html, self.get_or_create_test_user(), ImportOptions())
|
import_netscape_html(test_html, self.get_or_create_test_user(), ImportOptions())
|
||||||
|
|
||||||
self.assertEqual(Bookmark.objects.count(), 1)
|
self.assertEqual(Bookmark.objects.count(), 1)
|
||||||
self.assertEqual(Bookmark.objects.all()[0].description, 'Example description')
|
self.assertEqual(Bookmark.objects.all()[0].description, "Example description")
|
||||||
self.assertEqual(Bookmark.objects.all()[0].notes, 'Updated notes')
|
self.assertEqual(Bookmark.objects.all()[0].notes, "Updated notes")
|
||||||
|
|
||||||
def test_schedule_snapshot_creation(self):
|
def test_schedule_snapshot_creation(self):
|
||||||
user = self.get_or_create_test_user()
|
user = self.get_or_create_test_user()
|
||||||
test_html = self.render_html(tags_html='')
|
test_html = self.render_html(tags_html="")
|
||||||
|
|
||||||
with patch.object(tasks, 'schedule_bookmarks_without_snapshots') as mock_schedule_bookmarks_without_snapshots:
|
with patch.object(
|
||||||
|
tasks, "schedule_bookmarks_without_snapshots"
|
||||||
|
) as mock_schedule_bookmarks_without_snapshots:
|
||||||
import_netscape_html(test_html, user)
|
import_netscape_html(test_html, user)
|
||||||
|
|
||||||
mock_schedule_bookmarks_without_snapshots.assert_called_once_with(user)
|
mock_schedule_bookmarks_without_snapshots.assert_called_once_with(user)
|
||||||
|
|
||||||
def test_schedule_favicon_loading(self):
|
def test_schedule_favicon_loading(self):
|
||||||
user = self.get_or_create_test_user()
|
user = self.get_or_create_test_user()
|
||||||
test_html = self.render_html(tags_html='')
|
test_html = self.render_html(tags_html="")
|
||||||
|
|
||||||
with patch.object(tasks, 'schedule_bookmarks_without_favicons') as mock_schedule_bookmarks_without_favicons:
|
with patch.object(
|
||||||
|
tasks, "schedule_bookmarks_without_favicons"
|
||||||
|
) as mock_schedule_bookmarks_without_favicons:
|
||||||
import_netscape_html(test_html, user)
|
import_netscape_html(test_html, user)
|
||||||
|
|
||||||
mock_schedule_bookmarks_without_favicons.assert_called_once_with(user)
|
mock_schedule_bookmarks_without_favicons.assert_called_once_with(user)
|
||||||
|
|
|
@ -13,7 +13,7 @@ class MetadataViewTestCase(TestCase):
|
||||||
"short_name": "linkding",
|
"short_name": "linkding",
|
||||||
"start_url": "bookmarks",
|
"start_url": "bookmarks",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"scope": "/"
|
"scope": "/",
|
||||||
}
|
}
|
||||||
self.assertDictEqual(response_body, expected_body)
|
self.assertDictEqual(response_body, expected_body)
|
||||||
|
|
||||||
|
@ -28,6 +28,6 @@ class MetadataViewTestCase(TestCase):
|
||||||
"short_name": "linkding",
|
"short_name": "linkding",
|
||||||
"start_url": "bookmarks",
|
"start_url": "bookmarks",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"scope": "/linkding/"
|
"scope": "/linkding/",
|
||||||
}
|
}
|
||||||
self.assertDictEqual(response_body, expected_body)
|
self.assertDictEqual(response_body, expected_body)
|
||||||
|
|
|
@ -13,18 +13,26 @@ class NavMenuTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
def test_should_respect_share_profile_setting(self):
|
def test_should_respect_share_profile_setting(self):
|
||||||
self.user.profile.enable_sharing = False
|
self.user.profile.enable_sharing = False
|
||||||
self.user.profile.save()
|
self.user.profile.save()
|
||||||
response = self.client.get(reverse('bookmarks:index'))
|
response = self.client.get(reverse("bookmarks:index"))
|
||||||
html = response.content.decode()
|
html = response.content.decode()
|
||||||
|
|
||||||
self.assertInHTML(f'''
|
self.assertInHTML(
|
||||||
|
f"""
|
||||||
<a href="{reverse('bookmarks:shared')}" class="btn btn-link">Shared</a>
|
<a href="{reverse('bookmarks:shared')}" class="btn btn-link">Shared</a>
|
||||||
''', html, count=0)
|
""",
|
||||||
|
html,
|
||||||
|
count=0,
|
||||||
|
)
|
||||||
|
|
||||||
self.user.profile.enable_sharing = True
|
self.user.profile.enable_sharing = True
|
||||||
self.user.profile.save()
|
self.user.profile.save()
|
||||||
response = self.client.get(reverse('bookmarks:index'))
|
response = self.client.get(reverse("bookmarks:index"))
|
||||||
html = response.content.decode()
|
html = response.content.decode()
|
||||||
|
|
||||||
self.assertInHTML(f'''
|
self.assertInHTML(
|
||||||
|
f"""
|
||||||
<a href="{reverse('bookmarks:shared')}" class="btn btn-link">Shared</a>
|
<a href="{reverse('bookmarks:shared')}" class="btn btn-link">Shared</a>
|
||||||
''', html, count=2)
|
""",
|
||||||
|
html,
|
||||||
|
count=2,
|
||||||
|
)
|
||||||
|
|
|
@ -7,7 +7,9 @@ from bookmarks.tests.helpers import BookmarkFactoryMixin
|
||||||
|
|
||||||
class PaginationTagTest(TestCase, BookmarkFactoryMixin):
|
class PaginationTagTest(TestCase, BookmarkFactoryMixin):
|
||||||
|
|
||||||
def render_template(self, num_items: int, page_size: int, current_page: int, url: str = '/test') -> str:
|
def render_template(
|
||||||
|
self, num_items: int, page_size: int, current_page: int, url: str = "/test"
|
||||||
|
) -> str:
|
||||||
rf = RequestFactory()
|
rf = RequestFactory()
|
||||||
request = rf.get(url)
|
request = rf.get(url)
|
||||||
request.user = self.get_or_create_test_user()
|
request.user = self.get_or_create_test_user()
|
||||||
|
@ -15,58 +17,88 @@ class PaginationTagTest(TestCase, BookmarkFactoryMixin):
|
||||||
paginator = Paginator(range(0, num_items), page_size)
|
paginator = Paginator(range(0, num_items), page_size)
|
||||||
page = paginator.page(current_page)
|
page = paginator.page(current_page)
|
||||||
|
|
||||||
context = RequestContext(request, {'page': page})
|
context = RequestContext(request, {"page": page})
|
||||||
template_to_render = Template(
|
template_to_render = Template("{% load pagination %}" "{% pagination page %}")
|
||||||
'{% load pagination %}'
|
|
||||||
'{% pagination page %}'
|
|
||||||
)
|
|
||||||
return template_to_render.render(context)
|
return template_to_render.render(context)
|
||||||
|
|
||||||
def assertPrevLinkDisabled(self, html: str):
|
def assertPrevLinkDisabled(self, html: str):
|
||||||
self.assertInHTML('''
|
self.assertInHTML(
|
||||||
|
"""
|
||||||
<li class="page-item disabled">
|
<li class="page-item disabled">
|
||||||
<a href="#" tabindex="-1">Previous</a>
|
<a href="#" tabindex="-1">Previous</a>
|
||||||
</li>
|
</li>
|
||||||
''', html)
|
""",
|
||||||
|
html,
|
||||||
|
)
|
||||||
|
|
||||||
def assertPrevLink(self, html: str, page_number: int, href: str = None):
|
def assertPrevLink(self, html: str, page_number: int, href: str = None):
|
||||||
href = href if href else '?page={0}'.format(page_number)
|
href = href if href else "?page={0}".format(page_number)
|
||||||
self.assertInHTML('''
|
self.assertInHTML(
|
||||||
|
"""
|
||||||
<li class="page-item">
|
<li class="page-item">
|
||||||
<a href="{0}" tabindex="-1">Previous</a>
|
<a href="{0}" tabindex="-1">Previous</a>
|
||||||
</li>
|
</li>
|
||||||
'''.format(href), html)
|
""".format(
|
||||||
|
href
|
||||||
|
),
|
||||||
|
html,
|
||||||
|
)
|
||||||
|
|
||||||
def assertNextLinkDisabled(self, html: str):
|
def assertNextLinkDisabled(self, html: str):
|
||||||
self.assertInHTML('''
|
self.assertInHTML(
|
||||||
|
"""
|
||||||
<li class="page-item disabled">
|
<li class="page-item disabled">
|
||||||
<a href="#" tabindex="-1">Next</a>
|
<a href="#" tabindex="-1">Next</a>
|
||||||
</li>
|
</li>
|
||||||
''', html)
|
""",
|
||||||
|
html,
|
||||||
|
)
|
||||||
|
|
||||||
def assertNextLink(self, html: str, page_number: int, href: str = None):
|
def assertNextLink(self, html: str, page_number: int, href: str = None):
|
||||||
href = href if href else '?page={0}'.format(page_number)
|
href = href if href else "?page={0}".format(page_number)
|
||||||
self.assertInHTML('''
|
self.assertInHTML(
|
||||||
|
"""
|
||||||
<li class="page-item">
|
<li class="page-item">
|
||||||
<a href="{0}" tabindex="-1">Next</a>
|
<a href="{0}" tabindex="-1">Next</a>
|
||||||
</li>
|
</li>
|
||||||
'''.format(href), html)
|
""".format(
|
||||||
|
href
|
||||||
|
),
|
||||||
|
html,
|
||||||
|
)
|
||||||
|
|
||||||
def assertPageLink(self, html: str, page_number: int, active: bool, count: int = 1, href: str = None):
|
def assertPageLink(
|
||||||
active_class = 'active' if active else ''
|
self,
|
||||||
href = href if href else '?page={0}'.format(page_number)
|
html: str,
|
||||||
self.assertInHTML('''
|
page_number: int,
|
||||||
|
active: bool,
|
||||||
|
count: int = 1,
|
||||||
|
href: str = None,
|
||||||
|
):
|
||||||
|
active_class = "active" if active else ""
|
||||||
|
href = href if href else "?page={0}".format(page_number)
|
||||||
|
self.assertInHTML(
|
||||||
|
"""
|
||||||
<li class="page-item {1}">
|
<li class="page-item {1}">
|
||||||
<a href="{2}">{0}</a>
|
<a href="{2}">{0}</a>
|
||||||
</li>
|
</li>
|
||||||
'''.format(page_number, active_class, href), html, count=count)
|
""".format(
|
||||||
|
page_number, active_class, href
|
||||||
|
),
|
||||||
|
html,
|
||||||
|
count=count,
|
||||||
|
)
|
||||||
|
|
||||||
def assertTruncationIndicators(self, html: str, count: int):
|
def assertTruncationIndicators(self, html: str, count: int):
|
||||||
self.assertInHTML('''
|
self.assertInHTML(
|
||||||
|
"""
|
||||||
<li class="page-item">
|
<li class="page-item">
|
||||||
<span>...</span>
|
<span>...</span>
|
||||||
</li>
|
</li>
|
||||||
''', html, count=count)
|
""",
|
||||||
|
html,
|
||||||
|
count=count,
|
||||||
|
)
|
||||||
|
|
||||||
def test_previous_disabled_on_page_1(self):
|
def test_previous_disabled_on_page_1(self):
|
||||||
rendered_template = self.render_template(100, 10, 1)
|
rendered_template = self.render_template(100, 10, 1)
|
||||||
|
@ -92,7 +124,12 @@ class PaginationTagTest(TestCase, BookmarkFactoryMixin):
|
||||||
rendered_template = self.render_template(100, 10, current_page)
|
rendered_template = self.render_template(100, 10, current_page)
|
||||||
for page_number in range(1, 10):
|
for page_number in range(1, 10):
|
||||||
expected_occurrences = 1 if page_number in expected_visible_pages else 0
|
expected_occurrences = 1 if page_number in expected_visible_pages else 0
|
||||||
self.assertPageLink(rendered_template, page_number, page_number == current_page, expected_occurrences)
|
self.assertPageLink(
|
||||||
|
rendered_template,
|
||||||
|
page_number,
|
||||||
|
page_number == current_page,
|
||||||
|
expected_occurrences,
|
||||||
|
)
|
||||||
self.assertTruncationIndicators(rendered_template, 1)
|
self.assertTruncationIndicators(rendered_template, 1)
|
||||||
|
|
||||||
def test_truncate_pages_middle(self):
|
def test_truncate_pages_middle(self):
|
||||||
|
@ -101,7 +138,12 @@ class PaginationTagTest(TestCase, BookmarkFactoryMixin):
|
||||||
rendered_template = self.render_template(100, 10, current_page)
|
rendered_template = self.render_template(100, 10, current_page)
|
||||||
for page_number in range(1, 10):
|
for page_number in range(1, 10):
|
||||||
expected_occurrences = 1 if page_number in expected_visible_pages else 0
|
expected_occurrences = 1 if page_number in expected_visible_pages else 0
|
||||||
self.assertPageLink(rendered_template, page_number, page_number == current_page, expected_occurrences)
|
self.assertPageLink(
|
||||||
|
rendered_template,
|
||||||
|
page_number,
|
||||||
|
page_number == current_page,
|
||||||
|
expected_occurrences,
|
||||||
|
)
|
||||||
self.assertTruncationIndicators(rendered_template, 2)
|
self.assertTruncationIndicators(rendered_template, 2)
|
||||||
|
|
||||||
def test_truncate_pages_near_end(self):
|
def test_truncate_pages_near_end(self):
|
||||||
|
@ -110,12 +152,23 @@ class PaginationTagTest(TestCase, BookmarkFactoryMixin):
|
||||||
rendered_template = self.render_template(100, 10, current_page)
|
rendered_template = self.render_template(100, 10, current_page)
|
||||||
for page_number in range(1, 10):
|
for page_number in range(1, 10):
|
||||||
expected_occurrences = 1 if page_number in expected_visible_pages else 0
|
expected_occurrences = 1 if page_number in expected_visible_pages else 0
|
||||||
self.assertPageLink(rendered_template, page_number, page_number == current_page, expected_occurrences)
|
self.assertPageLink(
|
||||||
|
rendered_template,
|
||||||
|
page_number,
|
||||||
|
page_number == current_page,
|
||||||
|
expected_occurrences,
|
||||||
|
)
|
||||||
self.assertTruncationIndicators(rendered_template, 1)
|
self.assertTruncationIndicators(rendered_template, 1)
|
||||||
|
|
||||||
def test_respects_search_parameters(self):
|
def test_respects_search_parameters(self):
|
||||||
rendered_template = self.render_template(100, 10, 2, url='/test?q=cake&sort=title_asc&page=2')
|
rendered_template = self.render_template(
|
||||||
self.assertPrevLink(rendered_template, 1, href='?q=cake&sort=title_asc&page=1')
|
100, 10, 2, url="/test?q=cake&sort=title_asc&page=2"
|
||||||
self.assertPageLink(rendered_template, 1, False, href='?q=cake&sort=title_asc&page=1')
|
)
|
||||||
self.assertPageLink(rendered_template, 2, True, href='?q=cake&sort=title_asc&page=2')
|
self.assertPrevLink(rendered_template, 1, href="?q=cake&sort=title_asc&page=1")
|
||||||
self.assertNextLink(rendered_template, 3, href='?q=cake&sort=title_asc&page=3')
|
self.assertPageLink(
|
||||||
|
rendered_template, 1, False, href="?q=cake&sort=title_asc&page=1"
|
||||||
|
)
|
||||||
|
self.assertPageLink(
|
||||||
|
rendered_template, 2, True, href="?q=cake&sort=title_asc&page=2"
|
||||||
|
)
|
||||||
|
self.assertNextLink(rendered_template, 3, href="?q=cake&sort=title_asc&page=3")
|
||||||
|
|
|
@ -9,7 +9,9 @@ from bookmarks.tests.helpers import ImportTestMixin, BookmarkHtmlTag
|
||||||
|
|
||||||
|
|
||||||
class ParserTestCase(TestCase, ImportTestMixin):
|
class ParserTestCase(TestCase, ImportTestMixin):
|
||||||
def assertTagsEqual(self, bookmarks: List[NetscapeBookmark], html_tags: List[BookmarkHtmlTag]):
|
def assertTagsEqual(
|
||||||
|
self, bookmarks: List[NetscapeBookmark], html_tags: List[BookmarkHtmlTag]
|
||||||
|
):
|
||||||
self.assertEqual(len(bookmarks), len(html_tags))
|
self.assertEqual(len(bookmarks), len(html_tags))
|
||||||
for bookmark in bookmarks:
|
for bookmark in bookmarks:
|
||||||
html_tag = html_tags[bookmarks.index(bookmark)]
|
html_tag = html_tags[bookmarks.index(bookmark)]
|
||||||
|
@ -23,14 +25,34 @@ class ParserTestCase(TestCase, ImportTestMixin):
|
||||||
|
|
||||||
def test_parse_bookmarks(self):
|
def test_parse_bookmarks(self):
|
||||||
html_tags = [
|
html_tags = [
|
||||||
BookmarkHtmlTag(href='https://example.com', title='Example title', description='Example description',
|
BookmarkHtmlTag(
|
||||||
add_date='1', tags='example-tag'),
|
href="https://example.com",
|
||||||
BookmarkHtmlTag(href='https://example.com/foo', title='Foo title', description='',
|
title="Example title",
|
||||||
add_date='2', tags=''),
|
description="Example description",
|
||||||
BookmarkHtmlTag(href='https://example.com/bar', title='Bar title', description='Bar description',
|
add_date="1",
|
||||||
add_date='3', tags='bar-tag, other-tag'),
|
tags="example-tag",
|
||||||
BookmarkHtmlTag(href='https://example.com/baz', title='Baz title', description='Baz description',
|
),
|
||||||
add_date='3', to_read=True),
|
BookmarkHtmlTag(
|
||||||
|
href="https://example.com/foo",
|
||||||
|
title="Foo title",
|
||||||
|
description="",
|
||||||
|
add_date="2",
|
||||||
|
tags="",
|
||||||
|
),
|
||||||
|
BookmarkHtmlTag(
|
||||||
|
href="https://example.com/bar",
|
||||||
|
title="Bar title",
|
||||||
|
description="Bar description",
|
||||||
|
add_date="3",
|
||||||
|
tags="bar-tag, other-tag",
|
||||||
|
),
|
||||||
|
BookmarkHtmlTag(
|
||||||
|
href="https://example.com/baz",
|
||||||
|
title="Baz title",
|
||||||
|
description="Baz description",
|
||||||
|
add_date="3",
|
||||||
|
to_read=True,
|
||||||
|
),
|
||||||
]
|
]
|
||||||
html = self.render_html(html_tags)
|
html = self.render_html(html_tags)
|
||||||
bookmarks = parse(html)
|
bookmarks = parse(html)
|
||||||
|
@ -45,10 +67,14 @@ class ParserTestCase(TestCase, ImportTestMixin):
|
||||||
|
|
||||||
def test_reset_properties_after_adding_bookmark(self):
|
def test_reset_properties_after_adding_bookmark(self):
|
||||||
html_tags = [
|
html_tags = [
|
||||||
BookmarkHtmlTag(href='https://example.com', title='Example title', description='Example description',
|
BookmarkHtmlTag(
|
||||||
add_date='1', tags='example-tag'),
|
href="https://example.com",
|
||||||
BookmarkHtmlTag(href='', title='', description='',
|
title="Example title",
|
||||||
add_date='', tags='')
|
description="Example description",
|
||||||
|
add_date="1",
|
||||||
|
tags="example-tag",
|
||||||
|
),
|
||||||
|
BookmarkHtmlTag(href="", title="", description="", add_date="", tags=""),
|
||||||
]
|
]
|
||||||
html = self.render_html(html_tags)
|
html = self.render_html(html_tags)
|
||||||
bookmarks = parse(html)
|
bookmarks = parse(html)
|
||||||
|
@ -57,59 +83,101 @@ class ParserTestCase(TestCase, ImportTestMixin):
|
||||||
|
|
||||||
def test_empty_title(self):
|
def test_empty_title(self):
|
||||||
html_tags = [
|
html_tags = [
|
||||||
BookmarkHtmlTag(href='https://example.com', title='', description='Example description',
|
BookmarkHtmlTag(
|
||||||
add_date='1', tags='example-tag'),
|
href="https://example.com",
|
||||||
|
title="",
|
||||||
|
description="Example description",
|
||||||
|
add_date="1",
|
||||||
|
tags="example-tag",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
html = self.render_html(tags_html='''
|
html = self.render_html(
|
||||||
|
tags_html="""
|
||||||
<DT><A HREF="https://example.com" ADD_DATE="1" TAGS="example-tag"></A>
|
<DT><A HREF="https://example.com" ADD_DATE="1" TAGS="example-tag"></A>
|
||||||
<DD>Example description
|
<DD>Example description
|
||||||
''')
|
"""
|
||||||
|
)
|
||||||
bookmarks = parse(html)
|
bookmarks = parse(html)
|
||||||
|
|
||||||
self.assertTagsEqual(bookmarks, html_tags)
|
self.assertTagsEqual(bookmarks, html_tags)
|
||||||
|
|
||||||
def test_with_closing_description_tag(self):
|
def test_with_closing_description_tag(self):
|
||||||
html_tags = [
|
html_tags = [
|
||||||
BookmarkHtmlTag(href='https://example.com', title='Example title', description='Example description',
|
BookmarkHtmlTag(
|
||||||
add_date='1', tags='example-tag'),
|
href="https://example.com",
|
||||||
BookmarkHtmlTag(href='https://foo.com', title='Foo title', description='',
|
title="Example title",
|
||||||
add_date='2', tags=''),
|
description="Example description",
|
||||||
|
add_date="1",
|
||||||
|
tags="example-tag",
|
||||||
|
),
|
||||||
|
BookmarkHtmlTag(
|
||||||
|
href="https://foo.com",
|
||||||
|
title="Foo title",
|
||||||
|
description="",
|
||||||
|
add_date="2",
|
||||||
|
tags="",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
html = self.render_html(tags_html='''
|
html = self.render_html(
|
||||||
|
tags_html="""
|
||||||
<DT><A HREF="https://example.com" ADD_DATE="1" TAGS="example-tag">Example title</A>
|
<DT><A HREF="https://example.com" ADD_DATE="1" TAGS="example-tag">Example title</A>
|
||||||
<DD>Example description</DD>
|
<DD>Example description</DD>
|
||||||
<DT><A HREF="https://foo.com" ADD_DATE="2">Foo title</A>
|
<DT><A HREF="https://foo.com" ADD_DATE="2">Foo title</A>
|
||||||
<DD></DD>
|
<DD></DD>
|
||||||
''')
|
"""
|
||||||
|
)
|
||||||
bookmarks = parse(html)
|
bookmarks = parse(html)
|
||||||
|
|
||||||
self.assertTagsEqual(bookmarks, html_tags)
|
self.assertTagsEqual(bookmarks, html_tags)
|
||||||
|
|
||||||
def test_description_tag_before_anchor_tag(self):
|
def test_description_tag_before_anchor_tag(self):
|
||||||
html_tags = [
|
html_tags = [
|
||||||
BookmarkHtmlTag(href='https://example.com', title='Example title', description='Example description',
|
BookmarkHtmlTag(
|
||||||
add_date='1', tags='example-tag'),
|
href="https://example.com",
|
||||||
BookmarkHtmlTag(href='https://foo.com', title='Foo title', description='',
|
title="Example title",
|
||||||
add_date='2', tags=''),
|
description="Example description",
|
||||||
|
add_date="1",
|
||||||
|
tags="example-tag",
|
||||||
|
),
|
||||||
|
BookmarkHtmlTag(
|
||||||
|
href="https://foo.com",
|
||||||
|
title="Foo title",
|
||||||
|
description="",
|
||||||
|
add_date="2",
|
||||||
|
tags="",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
html = self.render_html(tags_html='''
|
html = self.render_html(
|
||||||
|
tags_html="""
|
||||||
<DT><DD>Example description</DD>
|
<DT><DD>Example description</DD>
|
||||||
<A HREF="https://example.com" ADD_DATE="1" TAGS="example-tag">Example title</A>
|
<A HREF="https://example.com" ADD_DATE="1" TAGS="example-tag">Example title</A>
|
||||||
<DT><DD></DD>
|
<DT><DD></DD>
|
||||||
<A HREF="https://foo.com" ADD_DATE="2">Foo title</A>
|
<A HREF="https://foo.com" ADD_DATE="2">Foo title</A>
|
||||||
''')
|
"""
|
||||||
|
)
|
||||||
bookmarks = parse(html)
|
bookmarks = parse(html)
|
||||||
|
|
||||||
self.assertTagsEqual(bookmarks, html_tags)
|
self.assertTagsEqual(bookmarks, html_tags)
|
||||||
|
|
||||||
def test_with_folders(self):
|
def test_with_folders(self):
|
||||||
html_tags = [
|
html_tags = [
|
||||||
BookmarkHtmlTag(href='https://example.com', title='Example title', description='Example description',
|
BookmarkHtmlTag(
|
||||||
add_date='1', tags='example-tag'),
|
href="https://example.com",
|
||||||
BookmarkHtmlTag(href='https://foo.com', title='Foo title', description='',
|
title="Example title",
|
||||||
add_date='2', tags=''),
|
description="Example description",
|
||||||
|
add_date="1",
|
||||||
|
tags="example-tag",
|
||||||
|
),
|
||||||
|
BookmarkHtmlTag(
|
||||||
|
href="https://foo.com",
|
||||||
|
title="Foo title",
|
||||||
|
description="",
|
||||||
|
add_date="2",
|
||||||
|
tags="",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
html = self.render_html(tags_html='''
|
html = self.render_html(
|
||||||
|
tags_html="""
|
||||||
<DL><p>
|
<DL><p>
|
||||||
<DT><H3>Folder 1</H3>
|
<DT><H3>Folder 1</H3>
|
||||||
<DL><p>
|
<DL><p>
|
||||||
|
@ -121,102 +189,126 @@ class ParserTestCase(TestCase, ImportTestMixin):
|
||||||
<DT><A HREF="https://foo.com" ADD_DATE="2">Foo title</A>
|
<DT><A HREF="https://foo.com" ADD_DATE="2">Foo title</A>
|
||||||
</DL><p>
|
</DL><p>
|
||||||
</DL><p>
|
</DL><p>
|
||||||
''')
|
"""
|
||||||
|
)
|
||||||
bookmarks = parse(html)
|
bookmarks = parse(html)
|
||||||
|
|
||||||
self.assertTagsEqual(bookmarks, html_tags)
|
self.assertTagsEqual(bookmarks, html_tags)
|
||||||
|
|
||||||
def test_private_flag(self):
|
def test_private_flag(self):
|
||||||
# is private by default
|
# is private by default
|
||||||
html = self.render_html(tags_html='''
|
html = self.render_html(
|
||||||
|
tags_html="""
|
||||||
<DT><A HREF="https://example.com" ADD_DATE="1">Example title</A>
|
<DT><A HREF="https://example.com" ADD_DATE="1">Example title</A>
|
||||||
<DD>Example description</DD>
|
<DD>Example description</DD>
|
||||||
''')
|
"""
|
||||||
|
)
|
||||||
bookmarks = parse(html)
|
bookmarks = parse(html)
|
||||||
self.assertEqual(bookmarks[0].private, True)
|
self.assertEqual(bookmarks[0].private, True)
|
||||||
|
|
||||||
# explicitly marked as private
|
# explicitly marked as private
|
||||||
html = self.render_html(tags_html='''
|
html = self.render_html(
|
||||||
|
tags_html="""
|
||||||
<DT><A HREF="https://example.com" ADD_DATE="1" PRIVATE="1">Example title</A>
|
<DT><A HREF="https://example.com" ADD_DATE="1" PRIVATE="1">Example title</A>
|
||||||
<DD>Example description</DD>
|
<DD>Example description</DD>
|
||||||
''')
|
"""
|
||||||
|
)
|
||||||
bookmarks = parse(html)
|
bookmarks = parse(html)
|
||||||
self.assertEqual(bookmarks[0].private, True)
|
self.assertEqual(bookmarks[0].private, True)
|
||||||
|
|
||||||
# explicitly marked as public
|
# explicitly marked as public
|
||||||
html = self.render_html(tags_html='''
|
html = self.render_html(
|
||||||
|
tags_html="""
|
||||||
<DT><A HREF="https://example.com" ADD_DATE="1" PRIVATE="0">Example title</A>
|
<DT><A HREF="https://example.com" ADD_DATE="1" PRIVATE="0">Example title</A>
|
||||||
<DD>Example description</DD>
|
<DD>Example description</DD>
|
||||||
''')
|
"""
|
||||||
|
)
|
||||||
bookmarks = parse(html)
|
bookmarks = parse(html)
|
||||||
self.assertEqual(bookmarks[0].private, False)
|
self.assertEqual(bookmarks[0].private, False)
|
||||||
|
|
||||||
def test_notes(self):
|
def test_notes(self):
|
||||||
# no description, no notes
|
# no description, no notes
|
||||||
html = self.render_html(tags_html='''
|
html = self.render_html(
|
||||||
|
tags_html="""
|
||||||
<DT><A HREF="https://example.com" ADD_DATE="1">Example title</A>
|
<DT><A HREF="https://example.com" ADD_DATE="1">Example title</A>
|
||||||
''')
|
"""
|
||||||
|
)
|
||||||
bookmarks = parse(html)
|
bookmarks = parse(html)
|
||||||
self.assertEqual(bookmarks[0].description, '')
|
self.assertEqual(bookmarks[0].description, "")
|
||||||
self.assertEqual(bookmarks[0].notes, '')
|
self.assertEqual(bookmarks[0].notes, "")
|
||||||
|
|
||||||
# description, no notes
|
# description, no notes
|
||||||
html = self.render_html(tags_html='''
|
html = self.render_html(
|
||||||
|
tags_html="""
|
||||||
<DT><A HREF="https://example.com" ADD_DATE="1">Example title</A>
|
<DT><A HREF="https://example.com" ADD_DATE="1">Example title</A>
|
||||||
<DD>Example description
|
<DD>Example description
|
||||||
''')
|
"""
|
||||||
|
)
|
||||||
bookmarks = parse(html)
|
bookmarks = parse(html)
|
||||||
self.assertEqual(bookmarks[0].description, 'Example description')
|
self.assertEqual(bookmarks[0].description, "Example description")
|
||||||
self.assertEqual(bookmarks[0].notes, '')
|
self.assertEqual(bookmarks[0].notes, "")
|
||||||
|
|
||||||
# description, notes
|
# description, notes
|
||||||
html = self.render_html(tags_html='''
|
html = self.render_html(
|
||||||
|
tags_html="""
|
||||||
<DT><A HREF="https://example.com" ADD_DATE="1">Example title</A>
|
<DT><A HREF="https://example.com" ADD_DATE="1">Example title</A>
|
||||||
<DD>Example description[linkding-notes]Example notes[/linkding-notes]
|
<DD>Example description[linkding-notes]Example notes[/linkding-notes]
|
||||||
''')
|
"""
|
||||||
|
)
|
||||||
bookmarks = parse(html)
|
bookmarks = parse(html)
|
||||||
self.assertEqual(bookmarks[0].description, 'Example description')
|
self.assertEqual(bookmarks[0].description, "Example description")
|
||||||
self.assertEqual(bookmarks[0].notes, 'Example notes')
|
self.assertEqual(bookmarks[0].notes, "Example notes")
|
||||||
|
|
||||||
# description, notes without closing tag
|
# description, notes without closing tag
|
||||||
html = self.render_html(tags_html='''
|
html = self.render_html(
|
||||||
|
tags_html="""
|
||||||
<DT><A HREF="https://example.com" ADD_DATE="1">Example title</A>
|
<DT><A HREF="https://example.com" ADD_DATE="1">Example title</A>
|
||||||
<DD>Example description[linkding-notes]Example notes
|
<DD>Example description[linkding-notes]Example notes
|
||||||
''')
|
"""
|
||||||
|
)
|
||||||
bookmarks = parse(html)
|
bookmarks = parse(html)
|
||||||
self.assertEqual(bookmarks[0].description, 'Example description')
|
self.assertEqual(bookmarks[0].description, "Example description")
|
||||||
self.assertEqual(bookmarks[0].notes, 'Example notes')
|
self.assertEqual(bookmarks[0].notes, "Example notes")
|
||||||
|
|
||||||
# no description, notes
|
# no description, notes
|
||||||
html = self.render_html(tags_html='''
|
html = self.render_html(
|
||||||
|
tags_html="""
|
||||||
<DT><A HREF="https://example.com" ADD_DATE="1">Example title</A>
|
<DT><A HREF="https://example.com" ADD_DATE="1">Example title</A>
|
||||||
<DD>[linkding-notes]Example notes[/linkding-notes]
|
<DD>[linkding-notes]Example notes[/linkding-notes]
|
||||||
''')
|
"""
|
||||||
|
)
|
||||||
bookmarks = parse(html)
|
bookmarks = parse(html)
|
||||||
self.assertEqual(bookmarks[0].description, '')
|
self.assertEqual(bookmarks[0].description, "")
|
||||||
self.assertEqual(bookmarks[0].notes, 'Example notes')
|
self.assertEqual(bookmarks[0].notes, "Example notes")
|
||||||
|
|
||||||
# notes reset between bookmarks
|
# notes reset between bookmarks
|
||||||
html = self.render_html(tags_html='''
|
html = self.render_html(
|
||||||
|
tags_html="""
|
||||||
<DT><A HREF="https://example.com/1" ADD_DATE="1">Example title</A>
|
<DT><A HREF="https://example.com/1" ADD_DATE="1">Example title</A>
|
||||||
<DD>[linkding-notes]Example notes[/linkding-notes]
|
<DD>[linkding-notes]Example notes[/linkding-notes]
|
||||||
<DT><A HREF="https://example.com/2" ADD_DATE="1">Example title</A>
|
<DT><A HREF="https://example.com/2" ADD_DATE="1">Example title</A>
|
||||||
<DD>Example description
|
<DD>Example description
|
||||||
''')
|
"""
|
||||||
|
)
|
||||||
bookmarks = parse(html)
|
bookmarks = parse(html)
|
||||||
self.assertEqual(bookmarks[0].description, '')
|
self.assertEqual(bookmarks[0].description, "")
|
||||||
self.assertEqual(bookmarks[0].notes, 'Example notes')
|
self.assertEqual(bookmarks[0].notes, "Example notes")
|
||||||
self.assertEqual(bookmarks[1].description, 'Example description')
|
self.assertEqual(bookmarks[1].description, "Example description")
|
||||||
self.assertEqual(bookmarks[1].notes, '')
|
self.assertEqual(bookmarks[1].notes, "")
|
||||||
|
|
||||||
def test_unescape_content(self):
|
def test_unescape_content(self):
|
||||||
html = self.render_html(tags_html='''
|
html = self.render_html(
|
||||||
|
tags_html="""
|
||||||
<DT><A HREF="https://example.com" ADD_DATE="1"><style>: The Style Information element</A>
|
<DT><A HREF="https://example.com" ADD_DATE="1"><style>: The Style Information element</A>
|
||||||
<DD>The <style> HTML element contains style information for a document, or part of a document.[linkding-notes]Interesting notes about the <style> HTML element.[/linkding-notes]
|
<DD>The <style> HTML element contains style information for a document, or part of a document.[linkding-notes]Interesting notes about the <style> HTML element.[/linkding-notes]
|
||||||
''')
|
"""
|
||||||
|
)
|
||||||
bookmarks = parse(html)
|
bookmarks = parse(html)
|
||||||
self.assertEqual(bookmarks[0].title,
|
self.assertEqual(bookmarks[0].title, "<style>: The Style Information element")
|
||||||
'<style>: The Style Information element')
|
self.assertEqual(
|
||||||
self.assertEqual(bookmarks[0].description,
|
bookmarks[0].description,
|
||||||
'The <style> HTML element contains style information for a document, or part of a document.')
|
"The <style> HTML element contains style information for a document, or part of a document.",
|
||||||
self.assertEqual(bookmarks[0].notes, 'Interesting notes about the <style> HTML element.')
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
bookmarks[0].notes, "Interesting notes about the <style> HTML element."
|
||||||
|
)
|
||||||
|
|
|
@ -7,49 +7,51 @@ from bookmarks.tests.helpers import BookmarkFactoryMixin
|
||||||
|
|
||||||
class PasswordChangeViewTestCase(TestCase, BookmarkFactoryMixin):
|
class PasswordChangeViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
self.user = User.objects.create_user('testuser', 'test@example.com', 'initial_password')
|
self.user = User.objects.create_user(
|
||||||
|
"testuser", "test@example.com", "initial_password"
|
||||||
|
)
|
||||||
self.client.force_login(self.user)
|
self.client.force_login(self.user)
|
||||||
|
|
||||||
def test_change_password(self):
|
def test_change_password(self):
|
||||||
form_data = {
|
form_data = {
|
||||||
'old_password': 'initial_password',
|
"old_password": "initial_password",
|
||||||
'new_password1': 'new_password',
|
"new_password1": "new_password",
|
||||||
'new_password2': 'new_password',
|
"new_password2": "new_password",
|
||||||
}
|
}
|
||||||
|
|
||||||
response = self.client.post(reverse('change_password'), form_data)
|
response = self.client.post(reverse("change_password"), form_data)
|
||||||
|
|
||||||
self.assertRedirects(response, reverse('password_change_done'))
|
self.assertRedirects(response, reverse("password_change_done"))
|
||||||
|
|
||||||
def test_change_password_done(self):
|
def test_change_password_done(self):
|
||||||
form_data = {
|
form_data = {
|
||||||
'old_password': 'initial_password',
|
"old_password": "initial_password",
|
||||||
'new_password1': 'new_password',
|
"new_password1": "new_password",
|
||||||
'new_password2': 'new_password',
|
"new_password2": "new_password",
|
||||||
}
|
}
|
||||||
|
|
||||||
response = self.client.post(reverse('change_password'), form_data, follow=True)
|
response = self.client.post(reverse("change_password"), form_data, follow=True)
|
||||||
|
|
||||||
self.assertContains(response, 'Your password was changed successfully')
|
self.assertContains(response, "Your password was changed successfully")
|
||||||
|
|
||||||
def test_should_return_error_for_invalid_old_password(self):
|
def test_should_return_error_for_invalid_old_password(self):
|
||||||
form_data = {
|
form_data = {
|
||||||
'old_password': 'wrong_password',
|
"old_password": "wrong_password",
|
||||||
'new_password1': 'new_password',
|
"new_password1": "new_password",
|
||||||
'new_password2': 'new_password',
|
"new_password2": "new_password",
|
||||||
}
|
}
|
||||||
|
|
||||||
response = self.client.post(reverse('change_password'), form_data)
|
response = self.client.post(reverse("change_password"), form_data)
|
||||||
|
|
||||||
self.assertIn('old_password', response.context_data['form'].errors)
|
self.assertIn("old_password", response.context_data["form"].errors)
|
||||||
|
|
||||||
def test_should_return_error_for_mismatching_new_password(self):
|
def test_should_return_error_for_mismatching_new_password(self):
|
||||||
form_data = {
|
form_data = {
|
||||||
'old_password': 'initial_password',
|
"old_password": "initial_password",
|
||||||
'new_password1': 'new_password',
|
"new_password1": "new_password",
|
||||||
'new_password2': 'wrong_password',
|
"new_password2": "wrong_password",
|
||||||
}
|
}
|
||||||
|
|
||||||
response = self.client.post(reverse('change_password'), form_data)
|
response = self.client.post(reverse("change_password"), form_data)
|
||||||
|
|
||||||
self.assertIn('new_password2', response.context_data['form'].errors)
|
self.assertIn("new_password2", response.context_data["form"].errors)
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -25,14 +25,13 @@ class SettingsExportViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
self.setup_bookmark(tags=[self.setup_tag()], is_archived=True)
|
self.setup_bookmark(tags=[self.setup_tag()], is_archived=True)
|
||||||
self.setup_bookmark(tags=[self.setup_tag()], is_archived=True)
|
self.setup_bookmark(tags=[self.setup_tag()], is_archived=True)
|
||||||
|
|
||||||
response = self.client.get(
|
response = self.client.get(reverse("bookmarks:settings.export"), follow=True)
|
||||||
reverse('bookmarks:settings.export'),
|
|
||||||
follow=True
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(response['content-type'], 'text/plain; charset=UTF-8')
|
self.assertEqual(response["content-type"], "text/plain; charset=UTF-8")
|
||||||
self.assertEqual(response['Content-Disposition'], 'attachment; filename="bookmarks.html"')
|
self.assertEqual(
|
||||||
|
response["Content-Disposition"], 'attachment; filename="bookmarks.html"'
|
||||||
|
)
|
||||||
|
|
||||||
for bookmark in Bookmark.objects.all():
|
for bookmark in Bookmark.objects.all():
|
||||||
self.assertContains(response, bookmark.url)
|
self.assertContains(response, bookmark.url)
|
||||||
|
@ -50,12 +49,9 @@ class SettingsExportViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
self.setup_bookmark(tags=[self.setup_tag()], user=other_user),
|
self.setup_bookmark(tags=[self.setup_tag()], user=other_user),
|
||||||
]
|
]
|
||||||
|
|
||||||
response = self.client.get(
|
response = self.client.get(reverse("bookmarks:settings.export"), follow=True)
|
||||||
reverse('bookmarks:settings.export'),
|
|
||||||
follow=True
|
|
||||||
)
|
|
||||||
|
|
||||||
text = response.content.decode('utf-8')
|
text = response.content.decode("utf-8")
|
||||||
|
|
||||||
for bookmark in owned_bookmarks:
|
for bookmark in owned_bookmarks:
|
||||||
self.assertIn(bookmark.url, text)
|
self.assertIn(bookmark.url, text)
|
||||||
|
@ -65,14 +61,22 @@ class SettingsExportViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
|
|
||||||
def test_should_check_authentication(self):
|
def test_should_check_authentication(self):
|
||||||
self.client.logout()
|
self.client.logout()
|
||||||
response = self.client.get(reverse('bookmarks:settings.export'), follow=True)
|
response = self.client.get(reverse("bookmarks:settings.export"), follow=True)
|
||||||
|
|
||||||
self.assertRedirects(response, reverse('login') + '?next=' + reverse('bookmarks:settings.export'))
|
self.assertRedirects(
|
||||||
|
response, reverse("login") + "?next=" + reverse("bookmarks:settings.export")
|
||||||
|
)
|
||||||
|
|
||||||
def test_should_show_hint_when_export_raises_error(self):
|
def test_should_show_hint_when_export_raises_error(self):
|
||||||
with patch('bookmarks.services.exporter.export_netscape_html') as mock_export_netscape_html:
|
with patch(
|
||||||
mock_export_netscape_html.side_effect = Exception('Nope')
|
"bookmarks.services.exporter.export_netscape_html"
|
||||||
response = self.client.get(reverse('bookmarks:settings.export'), follow=True)
|
) as mock_export_netscape_html:
|
||||||
|
mock_export_netscape_html.side_effect = Exception("Nope")
|
||||||
|
response = self.client.get(
|
||||||
|
reverse("bookmarks:settings.export"), follow=True
|
||||||
|
)
|
||||||
|
|
||||||
self.assertTemplateUsed(response, 'settings/general.html')
|
self.assertTemplateUsed(response, "settings/general.html")
|
||||||
self.assertFormErrorHint(response, 'An error occurred during bookmark export.')
|
self.assertFormErrorHint(
|
||||||
|
response, "An error occurred during bookmark export."
|
||||||
|
)
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue