Fix some type hints

This commit is contained in:
Sascha Ißbrücker
2025-03-09 11:30:13 +01:00
parent 4260dfce79
commit 1a1092d03a
16 changed files with 121 additions and 162 deletions

View File

@@ -19,6 +19,7 @@ from bookmarks.api.serializers import (
) )
from bookmarks.models import Bookmark, BookmarkAsset, BookmarkSearch, Tag, User from bookmarks.models import Bookmark, BookmarkAsset, BookmarkSearch, Tag, User
from bookmarks.services import assets, bookmarks, auto_tagging, website_loader from bookmarks.services import assets, bookmarks, auto_tagging, website_loader
from bookmarks.type_defs import HttpRequest
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -31,6 +32,7 @@ class BookmarkViewSet(
mixins.UpdateModelMixin, mixins.UpdateModelMixin,
mixins.DestroyModelMixin, mixins.DestroyModelMixin,
): ):
request: HttpRequest
serializer_class = BookmarkSerializer serializer_class = BookmarkSerializer
def get_permissions(self): def get_permissions(self):
@@ -73,27 +75,27 @@ class BookmarkViewSet(
} }
@action(methods=["get"], detail=False) @action(methods=["get"], detail=False)
def archived(self, request): def archived(self, request: HttpRequest):
return self.list(request) return self.list(request)
@action(methods=["get"], detail=False) @action(methods=["get"], detail=False)
def shared(self, request): def shared(self, request: HttpRequest):
return self.list(request) return self.list(request)
@action(methods=["post"], detail=True) @action(methods=["post"], detail=True)
def archive(self, request, pk): def archive(self, request: HttpRequest, pk):
bookmark = self.get_object() bookmark = self.get_object()
bookmarks.archive_bookmark(bookmark) bookmarks.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: HttpRequest, pk):
bookmark = self.get_object() bookmark = self.get_object()
bookmarks.unarchive_bookmark(bookmark) bookmarks.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: HttpRequest):
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 = ( existing_bookmark_data = (
@@ -124,13 +126,13 @@ class BookmarkViewSet(
) )
@action(methods=["post"], detail=False) @action(methods=["post"], detail=False)
def singlefile(self, request): def singlefile(self, request: HttpRequest):
if settings.LD_DISABLE_ASSET_UPLOAD: if settings.LD_DISABLE_ASSET_UPLOAD:
return Response( return Response(
{"error": "Asset upload is disabled."}, {"error": "Asset upload is disabled."},
status=status.HTTP_403_FORBIDDEN, status=status.HTTP_403_FORBIDDEN,
) )
url = request.data.get("url") url = request.POST.get("url")
file = request.FILES.get("file") file = request.FILES.get("file")
if not url or not file: if not url or not file:
@@ -162,6 +164,7 @@ class BookmarkAssetViewSet(
mixins.RetrieveModelMixin, mixins.RetrieveModelMixin,
mixins.DestroyModelMixin, mixins.DestroyModelMixin,
): ):
request: HttpRequest
serializer_class = BookmarkAssetSerializer serializer_class = BookmarkAssetSerializer
def get_queryset(self): def get_queryset(self):
@@ -177,7 +180,7 @@ class BookmarkAssetViewSet(
return {"user": self.request.user} return {"user": self.request.user}
@action(detail=True, methods=["get"], url_path="download") @action(detail=True, methods=["get"], url_path="download")
def download(self, request, bookmark_id, pk): def download(self, request: HttpRequest, bookmark_id, pk):
asset = self.get_object() asset = self.get_object()
try: try:
file_path = os.path.join(settings.LD_ASSET_FOLDER, asset.file) file_path = os.path.join(settings.LD_ASSET_FOLDER, asset.file)
@@ -205,7 +208,7 @@ class BookmarkAssetViewSet(
return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR) return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR)
@action(methods=["post"], detail=False) @action(methods=["post"], detail=False)
def upload(self, request, bookmark_id): def upload(self, request: HttpRequest, bookmark_id):
if settings.LD_DISABLE_ASSET_UPLOAD: if settings.LD_DISABLE_ASSET_UPLOAD:
return Response( return Response(
{"error": "Asset upload is disabled."}, {"error": "Asset upload is disabled."},
@@ -242,6 +245,7 @@ class TagViewSet(
mixins.RetrieveModelMixin, mixins.RetrieveModelMixin,
mixins.CreateModelMixin, mixins.CreateModelMixin,
): ):
request: HttpRequest
serializer_class = TagSerializer serializer_class = TagSerializer
def get_queryset(self): def get_queryset(self):
@@ -254,7 +258,7 @@ class TagViewSet(
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: HttpRequest):
return Response(UserProfileSerializer(request.user.profile).data) return Response(UserProfileSerializer(request.user.profile).data)

View File

@@ -22,6 +22,11 @@ class BookmarkListSerializer(ListSerializer):
return super().to_representation(data) return super().to_representation(data)
class EmtpyField(serializers.ReadOnlyField):
def to_representation(self, value):
return None
class BookmarkSerializer(serializers.ModelSerializer): class BookmarkSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Bookmark model = Bookmark
@@ -62,8 +67,8 @@ class BookmarkSerializer(serializers.ModelSerializer):
preview_image_url = serializers.SerializerMethodField() preview_image_url = serializers.SerializerMethodField()
web_archive_snapshot_url = serializers.SerializerMethodField() web_archive_snapshot_url = serializers.SerializerMethodField()
# Add dummy website title and description fields for backwards compatibility but keep them empty # Add dummy website title and description fields for backwards compatibility but keep them empty
website_title = serializers.SerializerMethodField() website_title = EmtpyField()
website_description = serializers.SerializerMethodField() website_description = EmtpyField()
def get_favicon_url(self, obj: Bookmark): def get_favicon_url(self, obj: Bookmark):
if not obj.favicon_file: if not obj.favicon_file:
@@ -87,12 +92,6 @@ class BookmarkSerializer(serializers.ModelSerializer):
return generate_fallback_webarchive_url(obj.url, obj.date_added) return generate_fallback_webarchive_url(obj.url, obj.date_added)
def get_website_title(self, obj: Bookmark):
return None
def get_website_description(self, obj: Bookmark):
return None
def create(self, validated_data): def create(self, validated_data):
tag_names = validated_data.pop("tag_names", []) tag_names = validated_data.pop("tag_names", [])
tag_string = build_tag_string(tag_names) tag_string = build_tag_string(tag_names)
@@ -185,9 +184,5 @@ class UserProfileSerializer(serializers.ModelSerializer):
"search_preferences", "search_preferences",
"version", "version",
] ]
read_only_fields = ["version"]
version = serializers.SerializerMethodField() version = serializers.ReadOnlyField(default=app_version)
def get_version(self, obj: UserProfile):
return app_version

View File

@@ -1,6 +1,5 @@
from bookmarks import queries
from bookmarks.models import BookmarkSearch, Toast
from bookmarks import utils from bookmarks import utils
from bookmarks.models import Toast
def toasts(request): def toasts(request):

View File

@@ -6,7 +6,6 @@ from typing import List
from django import forms from django import forms
from django.conf import settings from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.validators import MinValueValidator from django.core.validators import MinValueValidator
from django.db import models from django.db import models
@@ -23,7 +22,7 @@ logger = logging.getLogger(__name__)
class Tag(models.Model): class Tag(models.Model):
name = models.CharField(max_length=64) name = models.CharField(max_length=64)
date_added = models.DateTimeField() date_added = models.DateTimeField()
owner = models.ForeignKey(get_user_model(), on_delete=models.CASCADE) owner = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self): def __str__(self):
return self.name return self.name
@@ -70,7 +69,7 @@ class Bookmark(models.Model):
date_added = models.DateTimeField() date_added = models.DateTimeField()
date_modified = models.DateTimeField() date_modified = models.DateTimeField()
date_accessed = models.DateTimeField(blank=True, null=True) date_accessed = models.DateTimeField(blank=True, null=True)
owner = models.ForeignKey(get_user_model(), on_delete=models.CASCADE) owner = models.ForeignKey(User, on_delete=models.CASCADE)
tags = models.ManyToManyField(Tag) tags = models.ManyToManyField(Tag)
@property @property
@@ -387,9 +386,7 @@ class UserProfile(models.Model):
(TAG_GROUPING_ALPHABETICAL, "Alphabetical"), (TAG_GROUPING_ALPHABETICAL, "Alphabetical"),
(TAG_GROUPING_DISABLED, "Disabled"), (TAG_GROUPING_DISABLED, "Disabled"),
] ]
user = models.OneToOneField( user = models.OneToOneField(User, related_name="profile", on_delete=models.CASCADE)
get_user_model(), related_name="profile", on_delete=models.CASCADE
)
theme = models.CharField( theme = models.CharField(
max_length=10, choices=THEME_CHOICES, blank=False, default=THEME_AUTO max_length=10, choices=THEME_CHOICES, blank=False, default=THEME_AUTO
) )
@@ -497,13 +494,13 @@ class UserProfileForm(forms.ModelForm):
] ]
@receiver(post_save, sender=get_user_model()) @receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs): def create_user_profile(sender, instance, created, **kwargs):
if created: if created:
UserProfile.objects.create(user=instance) UserProfile.objects.create(user=instance)
@receiver(post_save, sender=get_user_model()) @receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs): def save_user_profile(sender, instance, **kwargs):
instance.profile.save() instance.profile.save()
@@ -512,7 +509,7 @@ class Toast(models.Model):
key = models.CharField(max_length=50) key = models.CharField(max_length=50)
message = models.TextField() message = models.TextField()
acknowledged = models.BooleanField(default=False) acknowledged = models.BooleanField(default=False)
owner = models.ForeignKey(get_user_model(), on_delete=models.CASCADE) owner = models.ForeignKey(User, on_delete=models.CASCADE)
class FeedToken(models.Model): class FeedToken(models.Model):
@@ -522,7 +519,7 @@ class FeedToken(models.Model):
key = models.CharField(max_length=40, primary_key=True) key = models.CharField(max_length=40, primary_key=True)
user = models.OneToOneField( user = models.OneToOneField(
get_user_model(), User,
related_name="feed_token", related_name="feed_token",
on_delete=models.CASCADE, on_delete=models.CASCADE,
) )
@@ -556,7 +553,7 @@ class GlobalSettings(models.Model):
default=LANDING_PAGE_LOGIN, default=LANDING_PAGE_LOGIN,
) )
guest_profile_user = models.ForeignKey( guest_profile_user = models.ForeignKey(
get_user_model(), on_delete=models.SET_NULL, null=True, blank=True User, on_delete=models.SET_NULL, null=True, blank=True
) )
enable_link_prefetch = models.BooleanField(default=False, null=False) enable_link_prefetch = models.BooleanField(default=False, null=False)

View File

@@ -1,10 +1,9 @@
import logging import logging
from typing import Union from typing import Union
from django.contrib.auth.models import User
from django.utils import timezone from django.utils import timezone
from bookmarks.models import Bookmark, parse_tag_string from bookmarks.models import Bookmark, User, parse_tag_string
from bookmarks.services import auto_tagging from bookmarks.services import auto_tagging
from bookmarks.services import tasks from bookmarks.services import tasks
from bookmarks.services import website_loader from bookmarks.services import website_loader

View File

@@ -4,7 +4,6 @@ from typing import List
import waybackpy import waybackpy
from django.conf import settings from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db.models import Q from django.db.models import Q
from huey import crontab from huey import crontab
@@ -157,7 +156,7 @@ def schedule_bookmarks_without_favicons(user: User):
@task() @task()
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 = User.objects.get(id=user_id)
bookmarks = Bookmark.objects.filter(favicon_file__exact="", owner=user) bookmarks = Bookmark.objects.filter(favicon_file__exact="", owner=user)
# TODO: Implement bulk task creation # TODO: Implement bulk task creation
@@ -173,7 +172,7 @@ def schedule_refresh_favicons(user: User):
@task() @task()
def _schedule_refresh_favicons_task(user_id: int): def _schedule_refresh_favicons_task(user_id: int):
user = get_user_model().objects.get(id=user_id) user = User.objects.get(id=user_id)
bookmarks = Bookmark.objects.filter(owner=user) bookmarks = Bookmark.objects.filter(owner=user)
# TODO: Implement bulk task creation # TODO: Implement bulk task creation
@@ -212,7 +211,7 @@ def schedule_bookmarks_without_previews(user: User):
@task() @task()
def _schedule_bookmarks_without_previews_task(user_id: int): def _schedule_bookmarks_without_previews_task(user_id: int):
user = get_user_model().objects.get(id=user_id) user = User.objects.get(id=user_id)
bookmarks = Bookmark.objects.filter( bookmarks = Bookmark.objects.filter(
Q(preview_image_file__exact=""), Q(preview_image_file__exact=""),
owner=user, owner=user,

View File

@@ -10,7 +10,6 @@ from unittest import TestCase
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import User
from django.test import override_settings from django.test import override_settings
from django.utils import timezone from django.utils import timezone
from django.utils.crypto import get_random_string from django.utils.crypto import get_random_string
@@ -18,7 +17,7 @@ from rest_framework import status
from rest_framework.authtoken.models import Token from rest_framework.authtoken.models import Token
from rest_framework.test import APITestCase from rest_framework.test import APITestCase
from bookmarks.models import Bookmark, BookmarkAsset, Tag from bookmarks.models import Bookmark, BookmarkAsset, Tag, User
class BookmarkFactoryMixin: class BookmarkFactoryMixin:

View File

@@ -138,8 +138,7 @@ class BookmarkNewViewTestCase(TestCase, BookmarkFactoryMixin):
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,
) )
@@ -148,7 +147,8 @@ class BookmarkNewViewTestCase(TestCase, BookmarkFactoryMixin):
html = response.content.decode() html = response.content.decode()
self.assertInHTML( self.assertInHTML(
'<input type="hidden" name="auto_close" id="id_auto_close">', html '<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):
@@ -264,7 +264,6 @@ class BookmarkNewViewTestCase(TestCase, BookmarkFactoryMixin):
html = response.content.decode() html = response.content.decode()
self.assertInHTML( self.assertInHTML(
'<input type="checkbox" name="unread" value="true" ' '<input type="checkbox" name="unread" id="id_unread" checked="">',
'id="id_unread" checked="">',
html, html,
) )

View File

@@ -1,12 +1,10 @@
import datetime import datetime
from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.test import TestCase, override_settings from django.test import TestCase, override_settings
from bookmarks.models import BookmarkForm, Bookmark from bookmarks.models import BookmarkForm, Bookmark
from bookmarks.tests.helpers import BookmarkFactoryMixin
User = get_user_model()
ENABLED_URL_VALIDATION_TEST_CASES = [ ENABLED_URL_VALIDATION_TEST_CASES = [
("thisisnotavalidurl", False), ("thisisnotavalidurl", False),
@@ -29,12 +27,10 @@ DISABLED_URL_VALIDATION_TEST_CASES = [
] ]
class BookmarkValidationTestCase(TestCase): class BookmarkValidationTestCase(TestCase, BookmarkFactoryMixin):
def setUp(self) -> None: def setUp(self) -> None:
self.user = User.objects.create_user( self.get_or_create_test_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(

View File

@@ -1,6 +1,5 @@
from unittest.mock import patch from unittest.mock import patch
from django.contrib.auth import get_user_model
from django.test import TestCase from django.test import TestCase
from django.utils import timezone from django.utils import timezone
@@ -25,8 +24,6 @@ from bookmarks.services.bookmarks import (
) )
from bookmarks.tests.helpers import BookmarkFactoryMixin from bookmarks.tests.helpers import BookmarkFactoryMixin
User = get_user_model()
class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin): class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin):
@@ -270,9 +267,7 @@ 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( other_user = self.setup_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)
@@ -327,9 +322,7 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin):
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( other_user = self.setup_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)
@@ -382,9 +375,7 @@ 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( other_user = self.setup_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)
@@ -508,9 +499,7 @@ 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( other_user = self.setup_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)
@@ -591,9 +580,7 @@ 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( other_user = self.setup_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])
@@ -658,9 +645,7 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin):
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( other_user = self.setup_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)
@@ -715,9 +700,7 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin):
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( other_user = self.setup_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)
@@ -770,9 +753,7 @@ 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( other_user = self.setup_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)
@@ -825,9 +806,7 @@ 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( other_user = self.setup_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)

View File

@@ -1,7 +1,6 @@
import operator
import datetime import datetime
import operator
from django.contrib.auth import get_user_model
from django.db.models import QuerySet from django.db.models import QuerySet
from django.test import TestCase from django.test import TestCase
from django.utils import timezone from django.utils import timezone
@@ -11,8 +10,6 @@ from bookmarks.models import BookmarkSearch, UserProfile
from bookmarks.tests.helpers import BookmarkFactoryMixin, random_sentence from bookmarks.tests.helpers import BookmarkFactoryMixin, random_sentence
from bookmarks.utils import unique from bookmarks.utils import unique
User = get_user_model()
class QueriesTestCase(TestCase, BookmarkFactoryMixin): class QueriesTestCase(TestCase, BookmarkFactoryMixin):
def setUp(self): def setUp(self):
@@ -372,9 +369,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
self.assertQueryResult(query, [[bookmark1, bookmark2]]) self.assertQueryResult(query, [[bookmark1, bookmark2]])
def test_query_bookmarks_should_only_return_user_owned_bookmarks(self): def test_query_bookmarks_should_only_return_user_owned_bookmarks(self):
other_user = User.objects.create_user( other_user = self.setup_user()
"otheruser", "otheruser@example.com", "password123"
)
owned_bookmarks = [ owned_bookmarks = [
self.setup_bookmark(), self.setup_bookmark(),
self.setup_bookmark(), self.setup_bookmark(),
@@ -389,9 +384,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
self.assertQueryResult(query, [owned_bookmarks]) self.assertQueryResult(query, [owned_bookmarks])
def test_query_archived_bookmarks_should_only_return_user_owned_bookmarks(self): def test_query_archived_bookmarks_should_only_return_user_owned_bookmarks(self):
other_user = User.objects.create_user( other_user = self.setup_user()
"otheruser", "otheruser@example.com", "password123"
)
owned_bookmarks = [ owned_bookmarks = [
self.setup_bookmark(is_archived=True), self.setup_bookmark(is_archived=True),
self.setup_bookmark(is_archived=True), self.setup_bookmark(is_archived=True),
@@ -828,9 +821,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
self.assertQueryResult(query, [[tag]]) self.assertQueryResult(query, [[tag]])
def test_query_bookmark_tags_should_only_return_user_owned_tags(self): def test_query_bookmark_tags_should_only_return_user_owned_tags(self):
other_user = User.objects.create_user( other_user = self.setup_user()
"otheruser", "otheruser@example.com", "password123"
)
owned_bookmarks = [ owned_bookmarks = [
self.setup_bookmark(tags=[self.setup_tag()]), self.setup_bookmark(tags=[self.setup_tag()]),
self.setup_bookmark(tags=[self.setup_tag()]), self.setup_bookmark(tags=[self.setup_tag()]),
@@ -847,9 +838,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
self.assertQueryResult(query, [self.get_tags_from_bookmarks(owned_bookmarks)]) self.assertQueryResult(query, [self.get_tags_from_bookmarks(owned_bookmarks)])
def test_query_archived_bookmark_tags_should_only_return_user_owned_tags(self): def test_query_archived_bookmark_tags_should_only_return_user_owned_tags(self):
other_user = User.objects.create_user( other_user = self.setup_user()
"otheruser", "otheruser@example.com", "password123"
)
owned_bookmarks = [ owned_bookmarks = [
self.setup_bookmark(is_archived=True, tags=[self.setup_tag()]), self.setup_bookmark(is_archived=True, tags=[self.setup_tag()]),
self.setup_bookmark(is_archived=True, tags=[self.setup_tag()]), self.setup_bookmark(is_archived=True, tags=[self.setup_tag()]),

View File

@@ -1,21 +1,17 @@
import datetime import datetime
from django.contrib.auth import get_user_model
from django.test import TestCase from django.test import TestCase
from django.utils import timezone from django.utils import timezone
from bookmarks.models import Tag from bookmarks.models import Tag
from bookmarks.services.tags import get_or_create_tag, get_or_create_tags from bookmarks.services.tags import get_or_create_tag, get_or_create_tags
from bookmarks.tests.helpers import BookmarkFactoryMixin
User = get_user_model()
class TagServiceTestCase(TestCase): class TagServiceTestCase(TestCase, BookmarkFactoryMixin):
def setUp(self) -> None: def setUp(self) -> None:
self.user = User.objects.create_user( self.get_or_create_test_user()
"testuser", "test@example.com", "password123"
)
def test_get_or_create_tag_should_create_new_tag(self): def test_get_or_create_tag_should_create_new_tag(self):
get_or_create_tag("Book", self.user) get_or_create_tag("Book", self.user)

14
bookmarks/type_defs.py Normal file
View File

@@ -0,0 +1,14 @@
"""
Stuff in here is only used for type hints
"""
from django import http
from django.contrib.auth.models import AnonymousUser
from bookmarks.models import GlobalSettings, UserProfile, User
class HttpRequest(http.HttpRequest):
global_settings: GlobalSettings
user_profile: UserProfile
user: User | AnonymousUser

View File

@@ -36,12 +36,13 @@ from bookmarks.services.bookmarks import (
share_bookmarks, share_bookmarks,
unshare_bookmarks, unshare_bookmarks,
) )
from bookmarks.type_defs import HttpRequest
from bookmarks.utils import get_safe_return_url from bookmarks.utils import get_safe_return_url
from bookmarks.views import contexts, partials, turbo from bookmarks.views import contexts, partials, turbo
@login_required @login_required
def index(request): def index(request: HttpRequest):
if request.method == "POST": if request.method == "POST":
return search_action(request) return search_action(request)
@@ -63,7 +64,7 @@ def index(request):
@login_required @login_required
def archived(request): def archived(request: HttpRequest):
if request.method == "POST": if request.method == "POST":
return search_action(request) return search_action(request)
@@ -84,7 +85,7 @@ def archived(request):
) )
def shared(request): def shared(request: HttpRequest):
if request.method == "POST": if request.method == "POST":
return search_action(request) return search_action(request)
@@ -110,7 +111,7 @@ def shared(request):
) )
def render_bookmarks_view(request, template_name, context): def render_bookmarks_view(request: HttpRequest, template_name, context):
if turbo.is_frame(request, "details-modal"): if turbo.is_frame(request, "details-modal"):
return render( return render(
request, request,
@@ -125,7 +126,7 @@ def render_bookmarks_view(request, template_name, context):
) )
def search_action(request): def search_action(request: HttpRequest):
if "save" in request.POST: if "save" in request.POST:
if not request.user.is_authenticated: if not request.user.is_authenticated:
return HttpResponseForbidden() return HttpResponseForbidden()
@@ -151,13 +152,8 @@ def convert_tag_string(tag_string: str):
@login_required @login_required
def new(request): def new(request: HttpRequest):
initial_url = request.GET.get("url") initial_auto_close = True if "auto_close" in request.GET else None
initial_title = request.GET.get("title")
initial_description = request.GET.get("description")
initial_notes = request.GET.get("notes")
initial_auto_close = "auto_close" in request.GET
initial_mark_unread = request.user.profile.default_mark_unread
if request.method == "POST": if request.method == "POST":
form = BookmarkForm(request.POST) form = BookmarkForm(request.POST)
@@ -171,19 +167,16 @@ def new(request):
else: else:
return HttpResponseRedirect(reverse("linkding:bookmarks.index")) return HttpResponseRedirect(reverse("linkding:bookmarks.index"))
else: else:
form = BookmarkForm() form = BookmarkForm(
if initial_url: initial={
form.initial["url"] = initial_url "url": request.GET.get("url"),
if initial_title: "title": request.GET.get("title"),
form.initial["title"] = initial_title "description": request.GET.get("description"),
if initial_description: "notes": request.GET.get("notes"),
form.initial["description"] = initial_description "auto_close": initial_auto_close,
if initial_notes: "unread": request.user_profile.default_mark_unread,
form.initial["notes"] = initial_notes }
if initial_auto_close: )
form.initial["auto_close"] = "true"
if initial_mark_unread:
form.initial["unread"] = "true"
status = 422 if request.method == "POST" and not form.is_valid() else 200 status = 422 if request.method == "POST" and not form.is_valid() else 200
context = { context = {
@@ -196,7 +189,7 @@ def new(request):
@login_required @login_required
def edit(request, bookmark_id: int): def edit(request: HttpRequest, bookmark_id: int):
try: try:
bookmark = Bookmark.objects.get(pk=bookmark_id, owner=request.user) bookmark = Bookmark.objects.get(pk=bookmark_id, owner=request.user)
except Bookmark.DoesNotExist: except Bookmark.DoesNotExist:
@@ -214,7 +207,7 @@ def edit(request, bookmark_id: int):
else: else:
form = BookmarkForm(instance=bookmark) form = BookmarkForm(instance=bookmark)
form.initial["tag_string"] = build_tag_string(bookmark.tag_names, " ") form.fields["tag_string"].initial = build_tag_string(bookmark.tag_names, " ")
status = 422 if request.method == "POST" and not form.is_valid() else 200 status = 422 if request.method == "POST" and not form.is_valid() else 200
context = {"form": form, "bookmark_id": bookmark_id, "return_url": return_url} context = {"form": form, "bookmark_id": bookmark_id, "return_url": return_url}
@@ -222,7 +215,7 @@ def edit(request, bookmark_id: int):
return render(request, "bookmarks/edit.html", context, status=status) return render(request, "bookmarks/edit.html", context, status=status)
def remove(request, bookmark_id: int): def remove(request: HttpRequest, bookmark_id: int | str):
try: try:
bookmark = Bookmark.objects.get(pk=bookmark_id, owner=request.user) bookmark = Bookmark.objects.get(pk=bookmark_id, owner=request.user)
except Bookmark.DoesNotExist: except Bookmark.DoesNotExist:
@@ -231,7 +224,7 @@ def remove(request, bookmark_id: int):
bookmark.delete() bookmark.delete()
def archive(request, bookmark_id: int): def archive(request: HttpRequest, bookmark_id: int | str):
try: try:
bookmark = Bookmark.objects.get(pk=bookmark_id, owner=request.user) bookmark = Bookmark.objects.get(pk=bookmark_id, owner=request.user)
except Bookmark.DoesNotExist: except Bookmark.DoesNotExist:
@@ -240,7 +233,7 @@ def archive(request, bookmark_id: int):
archive_bookmark(bookmark) archive_bookmark(bookmark)
def unarchive(request, bookmark_id: int): def unarchive(request: HttpRequest, bookmark_id: int | str):
try: try:
bookmark = Bookmark.objects.get(pk=bookmark_id, owner=request.user) bookmark = Bookmark.objects.get(pk=bookmark_id, owner=request.user)
except Bookmark.DoesNotExist: except Bookmark.DoesNotExist:
@@ -249,7 +242,7 @@ def unarchive(request, bookmark_id: int):
unarchive_bookmark(bookmark) unarchive_bookmark(bookmark)
def unshare(request, bookmark_id: int): def unshare(request: HttpRequest, bookmark_id: int | str):
try: try:
bookmark = Bookmark.objects.get(pk=bookmark_id, owner=request.user) bookmark = Bookmark.objects.get(pk=bookmark_id, owner=request.user)
except Bookmark.DoesNotExist: except Bookmark.DoesNotExist:
@@ -259,7 +252,7 @@ def unshare(request, bookmark_id: int):
bookmark.save() bookmark.save()
def mark_as_read(request, bookmark_id: int): def mark_as_read(request: HttpRequest, bookmark_id: int | str):
try: try:
bookmark = Bookmark.objects.get(pk=bookmark_id, owner=request.user) bookmark = Bookmark.objects.get(pk=bookmark_id, owner=request.user)
except Bookmark.DoesNotExist: except Bookmark.DoesNotExist:
@@ -269,7 +262,7 @@ def mark_as_read(request, bookmark_id: int):
bookmark.save() bookmark.save()
def create_html_snapshot(request, bookmark_id: int): def create_html_snapshot(request: HttpRequest, bookmark_id: int | str):
try: try:
bookmark = Bookmark.objects.get(pk=bookmark_id, owner=request.user) bookmark = Bookmark.objects.get(pk=bookmark_id, owner=request.user)
except Bookmark.DoesNotExist: except Bookmark.DoesNotExist:
@@ -278,7 +271,7 @@ def create_html_snapshot(request, bookmark_id: int):
tasks.create_html_snapshot(bookmark) tasks.create_html_snapshot(bookmark)
def upload_asset(request, bookmark_id: int): def upload_asset(request: HttpRequest, bookmark_id: int | str):
if settings.LD_DISABLE_ASSET_UPLOAD: if settings.LD_DISABLE_ASSET_UPLOAD:
return HttpResponseForbidden("Asset upload is disabled") return HttpResponseForbidden("Asset upload is disabled")
@@ -294,7 +287,7 @@ def upload_asset(request, bookmark_id: int):
asset_actions.upload_asset(bookmark, file) asset_actions.upload_asset(bookmark, file)
def remove_asset(request, asset_id: int): def remove_asset(request: HttpRequest, asset_id: int | str):
try: try:
asset = BookmarkAsset.objects.get(pk=asset_id, bookmark__owner=request.user) asset = BookmarkAsset.objects.get(pk=asset_id, bookmark__owner=request.user)
except BookmarkAsset.DoesNotExist: except BookmarkAsset.DoesNotExist:
@@ -303,7 +296,7 @@ def remove_asset(request, asset_id: int):
asset.delete() asset.delete()
def update_state(request, bookmark_id: int): def update_state(request: HttpRequest, bookmark_id: int | str):
try: try:
bookmark = Bookmark.objects.get(pk=bookmark_id, owner=request.user) bookmark = Bookmark.objects.get(pk=bookmark_id, owner=request.user)
except Bookmark.DoesNotExist: except Bookmark.DoesNotExist:
@@ -316,7 +309,7 @@ def update_state(request, bookmark_id: int):
@login_required @login_required
def index_action(request): def index_action(request: HttpRequest):
search = BookmarkSearch.from_request(request.GET) search = BookmarkSearch.from_request(request.GET)
query = queries.query_bookmarks(request.user, request.user_profile, search) query = queries.query_bookmarks(request.user, request.user_profile, search)
@@ -331,7 +324,7 @@ def index_action(request):
@login_required @login_required
def archived_action(request): def archived_action(request: HttpRequest):
search = BookmarkSearch.from_request(request.GET) search = BookmarkSearch.from_request(request.GET)
query = queries.query_archived_bookmarks(request.user, request.user_profile, search) query = queries.query_archived_bookmarks(request.user, request.user_profile, search)
@@ -346,7 +339,7 @@ def archived_action(request):
@login_required @login_required
def shared_action(request): def shared_action(request: HttpRequest):
if "bulk_execute" in request.POST: if "bulk_execute" in request.POST:
return HttpResponseBadRequest("View does not support bulk actions") return HttpResponseBadRequest("View does not support bulk actions")
@@ -360,7 +353,7 @@ def shared_action(request):
return utils.redirect_with_query(request, reverse("linkding:bookmarks.shared")) return utils.redirect_with_query(request, reverse("linkding:bookmarks.shared"))
def handle_action(request, query: QuerySet[Bookmark] = None): def handle_action(request: HttpRequest, query: QuerySet[Bookmark] = None):
# Single bookmark actions # Single bookmark actions
if "archive" in request.POST: if "archive" in request.POST:
return archive(request, request.POST["archive"]) return archive(request, request.POST["archive"])
@@ -421,5 +414,5 @@ def handle_action(request, query: QuerySet[Bookmark] = None):
@login_required @login_required
def close(request): def close(request: HttpRequest):
return render(request, "bookmarks/close.html") return render(request, "bookmarks/close.html")

View File

@@ -3,7 +3,6 @@ import urllib.parse
from typing import Set, List from typing import Set, List
from django.conf import settings from django.conf import settings
from django.core.handlers.wsgi import WSGIRequest
from django.core.paginator import Paginator from django.core.paginator import Paginator
from django.db import models from django.db import models
from django.http import Http404 from django.http import Http404
@@ -20,6 +19,7 @@ from bookmarks.models import (
Tag, Tag,
) )
from bookmarks.services.wayback import generate_fallback_webarchive_url from bookmarks.services.wayback import generate_fallback_webarchive_url
from bookmarks.type_defs import HttpRequest
CJK_RE = re.compile(r"[\u4e00-\u9fff]+") CJK_RE = re.compile(r"[\u4e00-\u9fff]+")
@@ -28,7 +28,7 @@ class RequestContext:
index_view = "linkding:bookmarks.index" index_view = "linkding:bookmarks.index"
action_view = "linkding:bookmarks.index.action" action_view = "linkding:bookmarks.index.action"
def __init__(self, request: WSGIRequest): def __init__(self, request: HttpRequest):
self.request = request self.request = request
self.index_url = reverse(self.index_view) self.index_url = reverse(self.index_view)
self.action_url = reverse(self.action_view) self.action_url = reverse(self.action_view)
@@ -168,7 +168,7 @@ class BookmarkItem:
class BookmarkListContext: class BookmarkListContext:
request_context = RequestContext request_context = RequestContext
def __init__(self, request: WSGIRequest) -> None: def __init__(self, request: HttpRequest) -> None:
request_context = self.request_context(request) request_context = self.request_context(request)
user = request.user user = request.user
user_profile = request.user_profile user_profile = request.user_profile
@@ -305,7 +305,7 @@ class TagGroup:
class TagCloudContext: class TagCloudContext:
request_context = RequestContext request_context = RequestContext
def __init__(self, request: WSGIRequest) -> None: def __init__(self, request: HttpRequest) -> None:
request_context = self.request_context(request) request_context = self.request_context(request)
user_profile = request.user_profile user_profile = request.user_profile
@@ -381,7 +381,7 @@ class BookmarkAssetItem:
class BookmarkDetailsContext: class BookmarkDetailsContext:
request_context = RequestContext request_context = RequestContext
def __init__(self, request: WSGIRequest, bookmark: Bookmark): def __init__(self, request: HttpRequest, bookmark: Bookmark):
request_context = self.request_context(request) request_context = self.request_context(request)
user = request.user user = request.user
@@ -437,7 +437,7 @@ class SharedBookmarkDetailsContext(BookmarkDetailsContext):
def get_details_context( def get_details_context(
request: WSGIRequest, context_type request: HttpRequest, context_type
) -> BookmarkDetailsContext | None: ) -> BookmarkDetailsContext | None:
bookmark_id = request.GET.get("details") bookmark_id = request.GET.get("details")
if not bookmark_id: if not bookmark_id:

View File

@@ -22,13 +22,14 @@ from bookmarks.models import (
) )
from bookmarks.services import exporter, tasks from bookmarks.services import exporter, tasks
from bookmarks.services import importer from bookmarks.services import importer
from bookmarks.type_defs import HttpRequest
from bookmarks.utils import app_version from bookmarks.utils import app_version
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@login_required @login_required
def general(request, status=200, context_overrides=None): def general(request: HttpRequest, status=200, context_overrides=None):
enable_refresh_favicons = django_settings.LD_ENABLE_REFRESH_FAVICONS enable_refresh_favicons = django_settings.LD_ENABLE_REFRESH_FAVICONS
has_snapshot_support = django_settings.LD_ENABLE_SNAPSHOTS has_snapshot_support = django_settings.LD_ENABLE_SNAPSHOTS
success_message = _find_message_with_tag( success_message = _find_message_with_tag(
@@ -65,7 +66,7 @@ def general(request, status=200, context_overrides=None):
@login_required @login_required
def update(request): def update(request: HttpRequest):
if request.method == "POST": if request.method == "POST":
if "update_profile" in request.POST: if "update_profile" in request.POST:
return update_profile(request) return update_profile(request)
@@ -97,7 +98,7 @@ def update(request):
return HttpResponseRedirect(reverse("linkding:settings.general")) return HttpResponseRedirect(reverse("linkding:settings.general"))
def update_profile(request): def update_profile(request: HttpRequest):
user = request.user user = request.user
profile = user.profile profile = user.profile
favicons_were_enabled = profile.enable_favicons favicons_were_enabled = profile.enable_favicons
@@ -195,7 +196,7 @@ def integrations(request):
@login_required @login_required
def bookmark_import(request): def bookmark_import(request: HttpRequest):
import_file = request.FILES.get("import_file") import_file = request.FILES.get("import_file")
import_options = importer.ImportOptions( import_options = importer.ImportOptions(
map_private_flag=request.POST.get("map_private_flag") == "on" map_private_flag=request.POST.get("map_private_flag") == "on"
@@ -230,13 +231,13 @@ def bookmark_import(request):
@login_required @login_required
def bookmark_export(request): def bookmark_export(request: HttpRequest):
# noinspection PyBroadException # noinspection PyBroadException
try: try:
bookmarks = Bookmark.objects.filter(owner=request.user) bookmarks = Bookmark.objects.filter(owner=request.user)
# Prefetch tags to prevent n+1 queries # Prefetch tags to prevent n+1 queries
prefetch_related_objects(bookmarks, "tags") prefetch_related_objects(bookmarks, "tags")
file_content = exporter.export_netscape_html(bookmarks) file_content = exporter.export_netscape_html(list(bookmarks))
response = HttpResponse(content_type="text/plain; charset=UTF-8") response = HttpResponse(content_type="text/plain; charset=UTF-8")
response["Content-Disposition"] = 'attachment; filename="bookmarks.html"' response["Content-Disposition"] = 'attachment; filename="bookmarks.html"'