Source code for users.feeds

"""RSS and Atom feeds for the users app."""

from __future__ import annotations

from typing import TYPE_CHECKING, Iterable

from django.conf import settings
from django.contrib.syndication.views import Feed
from django.db.models import Subquery
from django.http import HttpResponse
from django.utils import timezone as tz
from django.utils.cache import patch_vary_headers
from django.utils.decorators import method_decorator
from django.utils.feedgenerator import Atom1Feed
from django.utils.http import http_date
from django.views.decorators.cache import cache_control

from MangAdventure.utils import HttpResponseUnauthorized

from reader.models import Chapter
from users.models import Bookmark, UserProfile

if TYPE_CHECKING:  # pragma: no cover
    from datetime import datetime  # isort:skip
    from django.http import HttpRequest  # isort:skip


[docs]@method_decorator(cache_control(private=True, max_age=600), '__call__') class BookmarksRSS(Feed): """RSS feed for a user's bookmarks.""" ttl = 600 link = '/user/bookmarks/' author_name = settings.CONFIG['NAME'] title = f'Bookmarks - {author_name}' description = 'Updates when a bookmarked series has a new release' item_guid_is_permalink = True
[docs] def __call__(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: """ Get the HTTP response of the feed. :param request: The original request. :return: | A :status:`401` response if the token is missing. | A :status:`403` response if the provided token is invalid. | A :status:`200` response with the bookmarks feed otherwise. """ if not (token := request.GET.get('token')): if not (header := request.META.get('HTTP_AUTHORIZATION')): return HttpResponseUnauthorized( b'A token is required to access the feed.', content_type='text/plain', realm='bookmarks feed' ) if header[:7] != 'Bearer ': return HttpResponse( b'Authorization header format is invalid.', status=403, content_type='text/plain' ) token = header[7:] try: obj = UserProfile.objects.only( 'token', 'user_id' ).get(token=token) except UserProfile.DoesNotExist: return HttpResponse( b'The provided token is invalid.', status=403, content_type='text/plain' ) feedgen = self.get_feed(obj, request) res = HttpResponse(content_type=feedgen.content_type) # type: ignore patch_vary_headers(res, ('Authorization',)) res['Last-Modified'] = http_date( feedgen.latest_post_date().timestamp() ) feedgen.write(res, 'utf-8') return res
[docs] def feed_url(self, obj: UserProfile) -> str: """ Get the feed's own URL. :param obj: The object of the feed. :return: The feed's URL. """ return '/user/bookmarks.rss?token=' + obj.token
[docs] def items(self, obj: UserProfile) -> Iterable[Chapter]: """ Get an iterable of the feed's items. :param obj: The object of the feed. :return: An iterable of ``Chapter`` objects. """ return Chapter.objects.only( 'title', 'volume', 'number', 'published', 'modified', 'series__slug', 'series__title', 'series__format' ).select_related('series').filter( published__lte=tz.now(), series__licensed=False, series_id__in=Subquery( Bookmark.objects.filter( user_id=obj.user_id ).values('series_id') ) ).order_by('-published')
[docs] def item_description(self, item: Chapter) -> str: """ Get the description of the item. :param item: A ``Chapter`` object. :return: The ``Chapter`` object as a string. """ desc = str(item) if settings.CONFIG['ALLOW_DLS']: domain = settings.CONFIG['DOMAIN'] scheme = settings.ACCOUNT_DEFAULT_HTTP_PROTOCOL url = item.get_absolute_url()[:-1] + '.cbz' desc = f'<a href="{scheme}://{domain}{url}">{desc}</a>' return desc
[docs] def item_pubdate(self, item: Chapter) -> datetime: """ Get the publication date of the item. :param item: A ``Chapter`` object. :return: The date the chapter was published. """ return item.published
[docs] def item_updateddate(self, item: Chapter) -> datetime: """ Get the update date of the item. :param item: A ``Chapter`` object. :return: The date the chapter was modified. """ return item.modified
[docs]@method_decorator(cache_control(private=True, max_age=600), '__call__') class BookmarksAtom(BookmarksRSS): """Atom feed for a user's bookmarks.""" feed_type = Atom1Feed subtitle = BookmarksRSS.description
[docs] def feed_url(self, obj: UserProfile) -> str: """ Get the feed's own URL. :param obj: The object of the feed. :return: The feed's URL. """ return '/user/bookmarks.atom?token=' + obj.token
__all__ = ['BookmarksRSS', 'BookmarksAtom']