diff --git a/bookmarks/api/routes.py b/bookmarks/api/routes.py index 75a91ac..2ecade3 100644 --- a/bookmarks/api/routes.py +++ b/bookmarks/api/routes.py @@ -19,6 +19,7 @@ from bookmarks.api.serializers import ( ) from bookmarks.models import Bookmark, BookmarkAsset, BookmarkSearch, Tag, User from bookmarks.services import assets, bookmarks, auto_tagging, website_loader +from bookmarks.type_defs import HttpRequest logger = logging.getLogger(__name__) @@ -31,6 +32,7 @@ class BookmarkViewSet( mixins.UpdateModelMixin, mixins.DestroyModelMixin, ): + request: HttpRequest serializer_class = BookmarkSerializer def get_permissions(self): @@ -73,27 +75,27 @@ class BookmarkViewSet( } @action(methods=["get"], detail=False) - def archived(self, request): + def archived(self, request: HttpRequest): return self.list(request) @action(methods=["get"], detail=False) - def shared(self, request): + def shared(self, request: HttpRequest): return self.list(request) @action(methods=["post"], detail=True) - def archive(self, request, pk): + def archive(self, request: HttpRequest, pk): bookmark = self.get_object() bookmarks.archive_bookmark(bookmark) return Response(status=status.HTTP_204_NO_CONTENT) @action(methods=["post"], detail=True) - def unarchive(self, request, pk): + def unarchive(self, request: HttpRequest, pk): bookmark = self.get_object() bookmarks.unarchive_bookmark(bookmark) return Response(status=status.HTTP_204_NO_CONTENT) @action(methods=["get"], detail=False) - def check(self, request): + def check(self, request: HttpRequest): url = request.GET.get("url") bookmark = Bookmark.objects.filter(owner=request.user, url=url).first() existing_bookmark_data = ( @@ -124,13 +126,13 @@ class BookmarkViewSet( ) @action(methods=["post"], detail=False) - def singlefile(self, request): + def singlefile(self, request: HttpRequest): if settings.LD_DISABLE_ASSET_UPLOAD: return Response( {"error": "Asset upload is disabled."}, status=status.HTTP_403_FORBIDDEN, ) - url = request.data.get("url") + url = request.POST.get("url") file = request.FILES.get("file") if not url or not file: @@ -162,6 +164,7 @@ class BookmarkAssetViewSet( mixins.RetrieveModelMixin, mixins.DestroyModelMixin, ): + request: HttpRequest serializer_class = BookmarkAssetSerializer def get_queryset(self): @@ -177,7 +180,7 @@ class BookmarkAssetViewSet( return {"user": self.request.user} @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() try: 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) @action(methods=["post"], detail=False) - def upload(self, request, bookmark_id): + def upload(self, request: HttpRequest, bookmark_id): if settings.LD_DISABLE_ASSET_UPLOAD: return Response( {"error": "Asset upload is disabled."}, @@ -242,6 +245,7 @@ class TagViewSet( mixins.RetrieveModelMixin, mixins.CreateModelMixin, ): + request: HttpRequest serializer_class = TagSerializer def get_queryset(self): @@ -254,7 +258,7 @@ class TagViewSet( class UserViewSet(viewsets.GenericViewSet): @action(methods=["get"], detail=False) - def profile(self, request): + def profile(self, request: HttpRequest): return Response(UserProfileSerializer(request.user.profile).data) diff --git a/bookmarks/api/serializers.py b/bookmarks/api/serializers.py index 905cdbc..6abedcc 100644 --- a/bookmarks/api/serializers.py +++ b/bookmarks/api/serializers.py @@ -22,6 +22,11 @@ class BookmarkListSerializer(ListSerializer): return super().to_representation(data) +class EmtpyField(serializers.ReadOnlyField): + def to_representation(self, value): + return None + + class BookmarkSerializer(serializers.ModelSerializer): class Meta: model = Bookmark @@ -62,8 +67,8 @@ class BookmarkSerializer(serializers.ModelSerializer): preview_image_url = serializers.SerializerMethodField() web_archive_snapshot_url = serializers.SerializerMethodField() # Add dummy website title and description fields for backwards compatibility but keep them empty - website_title = serializers.SerializerMethodField() - website_description = serializers.SerializerMethodField() + website_title = EmtpyField() + website_description = EmtpyField() def get_favicon_url(self, obj: Bookmark): if not obj.favicon_file: @@ -87,12 +92,6 @@ class BookmarkSerializer(serializers.ModelSerializer): 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): tag_names = validated_data.pop("tag_names", []) tag_string = build_tag_string(tag_names) @@ -185,9 +184,5 @@ class UserProfileSerializer(serializers.ModelSerializer): "search_preferences", "version", ] - read_only_fields = ["version"] - version = serializers.SerializerMethodField() - - def get_version(self, obj: UserProfile): - return app_version + version = serializers.ReadOnlyField(default=app_version) diff --git a/bookmarks/context_processors.py b/bookmarks/context_processors.py index 1374712..38cd320 100644 --- a/bookmarks/context_processors.py +++ b/bookmarks/context_processors.py @@ -1,6 +1,5 @@ -from bookmarks import queries -from bookmarks.models import BookmarkSearch, Toast from bookmarks import utils +from bookmarks.models import Toast def toasts(request): diff --git a/bookmarks/models.py b/bookmarks/models.py index 0dd7e6e..72b11d2 100644 --- a/bookmarks/models.py +++ b/bookmarks/models.py @@ -6,7 +6,6 @@ from typing import List from django import forms from django.conf import settings -from django.contrib.auth import get_user_model from django.contrib.auth.models import User from django.core.validators import MinValueValidator from django.db import models @@ -23,7 +22,7 @@ logger = logging.getLogger(__name__) class Tag(models.Model): name = models.CharField(max_length=64) 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): return self.name @@ -70,7 +69,7 @@ class Bookmark(models.Model): date_added = models.DateTimeField() date_modified = models.DateTimeField() 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) @property @@ -387,9 +386,7 @@ class UserProfile(models.Model): (TAG_GROUPING_ALPHABETICAL, "Alphabetical"), (TAG_GROUPING_DISABLED, "Disabled"), ] - user = models.OneToOneField( - get_user_model(), related_name="profile", on_delete=models.CASCADE - ) + user = models.OneToOneField(User, related_name="profile", on_delete=models.CASCADE) theme = models.CharField( 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): if created: UserProfile.objects.create(user=instance) -@receiver(post_save, sender=get_user_model()) +@receiver(post_save, sender=User) def save_user_profile(sender, instance, **kwargs): instance.profile.save() @@ -512,7 +509,7 @@ class Toast(models.Model): key = models.CharField(max_length=50) message = models.TextField() 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): @@ -522,7 +519,7 @@ class FeedToken(models.Model): key = models.CharField(max_length=40, primary_key=True) user = models.OneToOneField( - get_user_model(), + User, related_name="feed_token", on_delete=models.CASCADE, ) @@ -556,7 +553,7 @@ class GlobalSettings(models.Model): default=LANDING_PAGE_LOGIN, ) 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) diff --git a/bookmarks/services/bookmarks.py b/bookmarks/services/bookmarks.py index d5bd7c2..c3e2f50 100644 --- a/bookmarks/services/bookmarks.py +++ b/bookmarks/services/bookmarks.py @@ -1,10 +1,9 @@ import logging from typing import Union -from django.contrib.auth.models import User 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 tasks from bookmarks.services import website_loader diff --git a/bookmarks/services/tasks.py b/bookmarks/services/tasks.py index 70406a9..3af0014 100644 --- a/bookmarks/services/tasks.py +++ b/bookmarks/services/tasks.py @@ -4,7 +4,6 @@ from typing import List import waybackpy from django.conf import settings -from django.contrib.auth import get_user_model from django.contrib.auth.models import User from django.db.models import Q from huey import crontab @@ -157,7 +156,7 @@ def schedule_bookmarks_without_favicons(user: User): @task() 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) # TODO: Implement bulk task creation @@ -173,7 +172,7 @@ def schedule_refresh_favicons(user: User): @task() 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) # TODO: Implement bulk task creation @@ -212,7 +211,7 @@ def schedule_bookmarks_without_previews(user: User): @task() 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( Q(preview_image_file__exact=""), owner=user, diff --git a/bookmarks/tests/helpers.py b/bookmarks/tests/helpers.py index 7fb760d..5607287 100644 --- a/bookmarks/tests/helpers.py +++ b/bookmarks/tests/helpers.py @@ -10,7 +10,6 @@ from unittest import TestCase from bs4 import BeautifulSoup from django.conf import settings -from django.contrib.auth.models import User from django.test import override_settings from django.utils import timezone 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.test import APITestCase -from bookmarks.models import Bookmark, BookmarkAsset, Tag +from bookmarks.models import Bookmark, BookmarkAsset, Tag, User class BookmarkFactoryMixin: diff --git a/bookmarks/tests/test_bookmark_new_view.py b/bookmarks/tests/test_bookmark_new_view.py index 9dbf346..abfee9f 100644 --- a/bookmarks/tests/test_bookmark_new_view.py +++ b/bookmarks/tests/test_bookmark_new_view.py @@ -138,8 +138,7 @@ class BookmarkNewViewTestCase(TestCase, BookmarkFactoryMixin): html = response.content.decode() self.assertInHTML( - '', + '', html, ) @@ -148,7 +147,8 @@ class BookmarkNewViewTestCase(TestCase, BookmarkFactoryMixin): html = response.content.decode() self.assertInHTML( - '', html + '', + html, ) def test_should_redirect_to_index_view(self): @@ -264,7 +264,6 @@ class BookmarkNewViewTestCase(TestCase, BookmarkFactoryMixin): html = response.content.decode() self.assertInHTML( - '', + '', html, ) diff --git a/bookmarks/tests/test_bookmark_validation.py b/bookmarks/tests/test_bookmark_validation.py index c26d2d7..151374f 100644 --- a/bookmarks/tests/test_bookmark_validation.py +++ b/bookmarks/tests/test_bookmark_validation.py @@ -1,12 +1,10 @@ import datetime -from django.contrib.auth import get_user_model from django.core.exceptions import ValidationError from django.test import TestCase, override_settings from bookmarks.models import BookmarkForm, Bookmark - -User = get_user_model() +from bookmarks.tests.helpers import BookmarkFactoryMixin ENABLED_URL_VALIDATION_TEST_CASES = [ ("thisisnotavalidurl", False), @@ -29,12 +27,10 @@ DISABLED_URL_VALIDATION_TEST_CASES = [ ] -class BookmarkValidationTestCase(TestCase): +class BookmarkValidationTestCase(TestCase, BookmarkFactoryMixin): def setUp(self) -> None: - self.user = User.objects.create_user( - "testuser", "test@example.com", "password123" - ) + self.get_or_create_test_user() def test_bookmark_model_should_not_allow_missing_url(self): bookmark = Bookmark( diff --git a/bookmarks/tests/test_bookmarks_service.py b/bookmarks/tests/test_bookmarks_service.py index 3cf91fe..1715b88 100644 --- a/bookmarks/tests/test_bookmarks_service.py +++ b/bookmarks/tests/test_bookmarks_service.py @@ -1,6 +1,5 @@ from unittest.mock import patch -from django.contrib.auth import get_user_model from django.test import TestCase from django.utils import timezone @@ -25,8 +24,6 @@ from bookmarks.services.bookmarks import ( ) from bookmarks.tests.helpers import BookmarkFactoryMixin -User = get_user_model() - class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin): @@ -270,9 +267,7 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin): self.assertTrue(Bookmark.objects.get(id=bookmark3.id).is_archived) def test_archive_bookmarks_should_only_archive_user_owned_bookmarks(self): - other_user = User.objects.create_user( - "otheruser", "otheruser@example.com", "password123" - ) + other_user = self.setup_user() bookmark1 = self.setup_bookmark() bookmark2 = self.setup_bookmark() 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) def test_unarchive_bookmarks_should_only_unarchive_user_owned_bookmarks(self): - other_user = User.objects.create_user( - "otheruser", "otheruser@example.com", "password123" - ) + other_user = self.setup_user() bookmark1 = self.setup_bookmark(is_archived=True) bookmark2 = self.setup_bookmark(is_archived=True) inaccessible_bookmark = self.setup_bookmark(is_archived=True, user=other_user) @@ -382,9 +375,7 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin): self.assertIsNone(Bookmark.objects.filter(id=bookmark3.id).first()) def test_delete_bookmarks_should_only_delete_user_owned_bookmarks(self): - other_user = User.objects.create_user( - "otheruser", "otheruser@example.com", "password123" - ) + other_user = self.setup_user() bookmark1 = self.setup_bookmark() bookmark2 = self.setup_bookmark() inaccessible_bookmark = self.setup_bookmark(user=other_user) @@ -508,9 +499,7 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin): self.assertCountEqual(bookmark3.tags.all(), [tag1, tag2]) def test_tag_bookmarks_should_only_tag_user_owned_bookmarks(self): - other_user = User.objects.create_user( - "otheruser", "otheruser@example.com", "password123" - ) + other_user = self.setup_user() bookmark1 = self.setup_bookmark() bookmark2 = self.setup_bookmark() inaccessible_bookmark = self.setup_bookmark(user=other_user) @@ -591,9 +580,7 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin): self.assertCountEqual(bookmark3.tags.all(), []) def test_untag_bookmarks_should_only_tag_user_owned_bookmarks(self): - other_user = User.objects.create_user( - "otheruser", "otheruser@example.com", "password123" - ) + other_user = self.setup_user() tag1 = self.setup_tag() tag2 = self.setup_tag() bookmark1 = self.setup_bookmark(tags=[tag1, tag2]) @@ -658,9 +645,7 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin): self.assertFalse(Bookmark.objects.get(id=bookmark3.id).unread) def test_mark_bookmarks_as_read_should_only_update_user_owned_bookmarks(self): - other_user = User.objects.create_user( - "otheruser", "otheruser@example.com", "password123" - ) + other_user = self.setup_user() bookmark1 = self.setup_bookmark(unread=True) bookmark2 = self.setup_bookmark(unread=True) 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) 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 = self.setup_user() bookmark1 = self.setup_bookmark(unread=False) bookmark2 = self.setup_bookmark(unread=False) 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) def test_share_bookmarks_should_only_update_user_owned_bookmarks(self): - other_user = User.objects.create_user( - "otheruser", "otheruser@example.com", "password123" - ) + other_user = self.setup_user() bookmark1 = self.setup_bookmark(shared=False) bookmark2 = self.setup_bookmark(shared=False) 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) def test_unshare_bookmarks_should_only_update_user_owned_bookmarks(self): - other_user = User.objects.create_user( - "otheruser", "otheruser@example.com", "password123" - ) + other_user = self.setup_user() bookmark1 = self.setup_bookmark(shared=True) bookmark2 = self.setup_bookmark(shared=True) inaccessible_bookmark = self.setup_bookmark(shared=True, user=other_user) diff --git a/bookmarks/tests/test_queries.py b/bookmarks/tests/test_queries.py index 429cfdd..e1b1908 100644 --- a/bookmarks/tests/test_queries.py +++ b/bookmarks/tests/test_queries.py @@ -1,7 +1,6 @@ -import operator import datetime +import operator -from django.contrib.auth import get_user_model from django.db.models import QuerySet from django.test import TestCase 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.utils import unique -User = get_user_model() - class QueriesTestCase(TestCase, BookmarkFactoryMixin): def setUp(self): @@ -372,9 +369,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin): self.assertQueryResult(query, [[bookmark1, bookmark2]]) def test_query_bookmarks_should_only_return_user_owned_bookmarks(self): - other_user = User.objects.create_user( - "otheruser", "otheruser@example.com", "password123" - ) + other_user = self.setup_user() owned_bookmarks = [ self.setup_bookmark(), self.setup_bookmark(), @@ -389,9 +384,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin): self.assertQueryResult(query, [owned_bookmarks]) def test_query_archived_bookmarks_should_only_return_user_owned_bookmarks(self): - other_user = User.objects.create_user( - "otheruser", "otheruser@example.com", "password123" - ) + other_user = self.setup_user() owned_bookmarks = [ self.setup_bookmark(is_archived=True), self.setup_bookmark(is_archived=True), @@ -828,9 +821,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin): self.assertQueryResult(query, [[tag]]) def test_query_bookmark_tags_should_only_return_user_owned_tags(self): - other_user = User.objects.create_user( - "otheruser", "otheruser@example.com", "password123" - ) + other_user = self.setup_user() owned_bookmarks = [ 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)]) def test_query_archived_bookmark_tags_should_only_return_user_owned_tags(self): - other_user = User.objects.create_user( - "otheruser", "otheruser@example.com", "password123" - ) + other_user = self.setup_user() owned_bookmarks = [ self.setup_bookmark(is_archived=True, tags=[self.setup_tag()]), self.setup_bookmark(is_archived=True, tags=[self.setup_tag()]), diff --git a/bookmarks/tests/test_tags_service.py b/bookmarks/tests/test_tags_service.py index 7560d2a..84fe5bb 100644 --- a/bookmarks/tests/test_tags_service.py +++ b/bookmarks/tests/test_tags_service.py @@ -1,21 +1,17 @@ import datetime -from django.contrib.auth import get_user_model from django.test import TestCase from django.utils import timezone from bookmarks.models import Tag from bookmarks.services.tags import get_or_create_tag, get_or_create_tags - -User = get_user_model() +from bookmarks.tests.helpers import BookmarkFactoryMixin -class TagServiceTestCase(TestCase): +class TagServiceTestCase(TestCase, BookmarkFactoryMixin): def setUp(self) -> None: - self.user = User.objects.create_user( - "testuser", "test@example.com", "password123" - ) + self.get_or_create_test_user() def test_get_or_create_tag_should_create_new_tag(self): get_or_create_tag("Book", self.user) diff --git a/bookmarks/type_defs.py b/bookmarks/type_defs.py new file mode 100644 index 0000000..4c1fe9f --- /dev/null +++ b/bookmarks/type_defs.py @@ -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 diff --git a/bookmarks/views/bookmarks.py b/bookmarks/views/bookmarks.py index ed05191..9314f69 100644 --- a/bookmarks/views/bookmarks.py +++ b/bookmarks/views/bookmarks.py @@ -36,12 +36,13 @@ from bookmarks.services.bookmarks import ( share_bookmarks, unshare_bookmarks, ) +from bookmarks.type_defs import HttpRequest from bookmarks.utils import get_safe_return_url from bookmarks.views import contexts, partials, turbo @login_required -def index(request): +def index(request: HttpRequest): if request.method == "POST": return search_action(request) @@ -63,7 +64,7 @@ def index(request): @login_required -def archived(request): +def archived(request: HttpRequest): if request.method == "POST": return search_action(request) @@ -84,7 +85,7 @@ def archived(request): ) -def shared(request): +def shared(request: HttpRequest): if request.method == "POST": 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"): return render( 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 not request.user.is_authenticated: return HttpResponseForbidden() @@ -151,13 +152,8 @@ def convert_tag_string(tag_string: str): @login_required -def new(request): - initial_url = request.GET.get("url") - 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 +def new(request: HttpRequest): + initial_auto_close = True if "auto_close" in request.GET else None if request.method == "POST": form = BookmarkForm(request.POST) @@ -171,19 +167,16 @@ def new(request): else: return HttpResponseRedirect(reverse("linkding:bookmarks.index")) else: - form = BookmarkForm() - if initial_url: - form.initial["url"] = initial_url - if initial_title: - form.initial["title"] = initial_title - if initial_description: - form.initial["description"] = initial_description - if initial_notes: - form.initial["notes"] = initial_notes - if initial_auto_close: - form.initial["auto_close"] = "true" - if initial_mark_unread: - form.initial["unread"] = "true" + form = BookmarkForm( + initial={ + "url": request.GET.get("url"), + "title": request.GET.get("title"), + "description": request.GET.get("description"), + "notes": request.GET.get("notes"), + "auto_close": initial_auto_close, + "unread": request.user_profile.default_mark_unread, + } + ) status = 422 if request.method == "POST" and not form.is_valid() else 200 context = { @@ -196,7 +189,7 @@ def new(request): @login_required -def edit(request, bookmark_id: int): +def edit(request: HttpRequest, bookmark_id: int): try: bookmark = Bookmark.objects.get(pk=bookmark_id, owner=request.user) except Bookmark.DoesNotExist: @@ -214,7 +207,7 @@ def edit(request, bookmark_id: int): else: 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 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) -def remove(request, bookmark_id: int): +def remove(request: HttpRequest, bookmark_id: int | str): try: bookmark = Bookmark.objects.get(pk=bookmark_id, owner=request.user) except Bookmark.DoesNotExist: @@ -231,7 +224,7 @@ def remove(request, bookmark_id: int): bookmark.delete() -def archive(request, bookmark_id: int): +def archive(request: HttpRequest, bookmark_id: int | str): try: bookmark = Bookmark.objects.get(pk=bookmark_id, owner=request.user) except Bookmark.DoesNotExist: @@ -240,7 +233,7 @@ def archive(request, bookmark_id: int): archive_bookmark(bookmark) -def unarchive(request, bookmark_id: int): +def unarchive(request: HttpRequest, bookmark_id: int | str): try: bookmark = Bookmark.objects.get(pk=bookmark_id, owner=request.user) except Bookmark.DoesNotExist: @@ -249,7 +242,7 @@ def unarchive(request, bookmark_id: int): unarchive_bookmark(bookmark) -def unshare(request, bookmark_id: int): +def unshare(request: HttpRequest, bookmark_id: int | str): try: bookmark = Bookmark.objects.get(pk=bookmark_id, owner=request.user) except Bookmark.DoesNotExist: @@ -259,7 +252,7 @@ def unshare(request, bookmark_id: int): bookmark.save() -def mark_as_read(request, bookmark_id: int): +def mark_as_read(request: HttpRequest, bookmark_id: int | str): try: bookmark = Bookmark.objects.get(pk=bookmark_id, owner=request.user) except Bookmark.DoesNotExist: @@ -269,7 +262,7 @@ def mark_as_read(request, bookmark_id: int): bookmark.save() -def create_html_snapshot(request, bookmark_id: int): +def create_html_snapshot(request: HttpRequest, bookmark_id: int | str): try: bookmark = Bookmark.objects.get(pk=bookmark_id, owner=request.user) except Bookmark.DoesNotExist: @@ -278,7 +271,7 @@ def create_html_snapshot(request, bookmark_id: int): 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: return HttpResponseForbidden("Asset upload is disabled") @@ -294,7 +287,7 @@ def upload_asset(request, bookmark_id: int): asset_actions.upload_asset(bookmark, file) -def remove_asset(request, asset_id: int): +def remove_asset(request: HttpRequest, asset_id: int | str): try: asset = BookmarkAsset.objects.get(pk=asset_id, bookmark__owner=request.user) except BookmarkAsset.DoesNotExist: @@ -303,7 +296,7 @@ def remove_asset(request, asset_id: int): asset.delete() -def update_state(request, bookmark_id: int): +def update_state(request: HttpRequest, bookmark_id: int | str): try: bookmark = Bookmark.objects.get(pk=bookmark_id, owner=request.user) except Bookmark.DoesNotExist: @@ -316,7 +309,7 @@ def update_state(request, bookmark_id: int): @login_required -def index_action(request): +def index_action(request: HttpRequest): search = BookmarkSearch.from_request(request.GET) query = queries.query_bookmarks(request.user, request.user_profile, search) @@ -331,7 +324,7 @@ def index_action(request): @login_required -def archived_action(request): +def archived_action(request: HttpRequest): search = BookmarkSearch.from_request(request.GET) query = queries.query_archived_bookmarks(request.user, request.user_profile, search) @@ -346,7 +339,7 @@ def archived_action(request): @login_required -def shared_action(request): +def shared_action(request: HttpRequest): if "bulk_execute" in request.POST: 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")) -def handle_action(request, query: QuerySet[Bookmark] = None): +def handle_action(request: HttpRequest, query: QuerySet[Bookmark] = None): # Single bookmark actions if "archive" in request.POST: return archive(request, request.POST["archive"]) @@ -421,5 +414,5 @@ def handle_action(request, query: QuerySet[Bookmark] = None): @login_required -def close(request): +def close(request: HttpRequest): return render(request, "bookmarks/close.html") diff --git a/bookmarks/views/contexts.py b/bookmarks/views/contexts.py index 3778d65..52ea1e1 100644 --- a/bookmarks/views/contexts.py +++ b/bookmarks/views/contexts.py @@ -3,7 +3,6 @@ import urllib.parse from typing import Set, List from django.conf import settings -from django.core.handlers.wsgi import WSGIRequest from django.core.paginator import Paginator from django.db import models from django.http import Http404 @@ -20,6 +19,7 @@ from bookmarks.models import ( Tag, ) from bookmarks.services.wayback import generate_fallback_webarchive_url +from bookmarks.type_defs import HttpRequest CJK_RE = re.compile(r"[\u4e00-\u9fff]+") @@ -28,7 +28,7 @@ class RequestContext: index_view = "linkding:bookmarks.index" action_view = "linkding:bookmarks.index.action" - def __init__(self, request: WSGIRequest): + def __init__(self, request: HttpRequest): self.request = request self.index_url = reverse(self.index_view) self.action_url = reverse(self.action_view) @@ -168,7 +168,7 @@ class BookmarkItem: class BookmarkListContext: request_context = RequestContext - def __init__(self, request: WSGIRequest) -> None: + def __init__(self, request: HttpRequest) -> None: request_context = self.request_context(request) user = request.user user_profile = request.user_profile @@ -305,7 +305,7 @@ class TagGroup: class TagCloudContext: request_context = RequestContext - def __init__(self, request: WSGIRequest) -> None: + def __init__(self, request: HttpRequest) -> None: request_context = self.request_context(request) user_profile = request.user_profile @@ -381,7 +381,7 @@ class BookmarkAssetItem: class BookmarkDetailsContext: request_context = RequestContext - def __init__(self, request: WSGIRequest, bookmark: Bookmark): + def __init__(self, request: HttpRequest, bookmark: Bookmark): request_context = self.request_context(request) user = request.user @@ -437,7 +437,7 @@ class SharedBookmarkDetailsContext(BookmarkDetailsContext): def get_details_context( - request: WSGIRequest, context_type + request: HttpRequest, context_type ) -> BookmarkDetailsContext | None: bookmark_id = request.GET.get("details") if not bookmark_id: diff --git a/bookmarks/views/settings.py b/bookmarks/views/settings.py index b71dcdc..abce0ce 100644 --- a/bookmarks/views/settings.py +++ b/bookmarks/views/settings.py @@ -22,13 +22,14 @@ from bookmarks.models import ( ) from bookmarks.services import exporter, tasks from bookmarks.services import importer +from bookmarks.type_defs import HttpRequest from bookmarks.utils import app_version logger = logging.getLogger(__name__) @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 has_snapshot_support = django_settings.LD_ENABLE_SNAPSHOTS success_message = _find_message_with_tag( @@ -65,7 +66,7 @@ def general(request, status=200, context_overrides=None): @login_required -def update(request): +def update(request: HttpRequest): if request.method == "POST": if "update_profile" in request.POST: return update_profile(request) @@ -97,7 +98,7 @@ def update(request): return HttpResponseRedirect(reverse("linkding:settings.general")) -def update_profile(request): +def update_profile(request: HttpRequest): user = request.user profile = user.profile favicons_were_enabled = profile.enable_favicons @@ -195,7 +196,7 @@ def integrations(request): @login_required -def bookmark_import(request): +def bookmark_import(request: HttpRequest): import_file = request.FILES.get("import_file") import_options = importer.ImportOptions( map_private_flag=request.POST.get("map_private_flag") == "on" @@ -230,13 +231,13 @@ def bookmark_import(request): @login_required -def bookmark_export(request): +def bookmark_export(request: HttpRequest): # noinspection PyBroadException try: bookmarks = Bookmark.objects.filter(owner=request.user) # Prefetch tags to prevent n+1 queries 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["Content-Disposition"] = 'attachment; filename="bookmarks.html"'