Source code for reader.feeds
"""RSS and Atom feeds for the reader app."""
from __future__ import annotations
from mimetypes import guess_type
from typing import TYPE_CHECKING, Iterable, Optional
from django.conf import settings
from django.contrib.syndication.views import Feed
from django.db.models import F, Prefetch
from django.utils import timezone as tz
from django.utils.decorators import method_decorator
from django.utils.feedgenerator import Atom1Feed
from django.views.decorators.cache import cache_control
from .models import Category, Chapter, Series
if TYPE_CHECKING: # pragma: no cover
from datetime import datetime # isort:skip
from django.http import HttpRequest # isort:skip
_max = settings.CONFIG['MAX_RELEASES']
[docs]@method_decorator(cache_control(public=True, max_age=600), '__call__')
class LibraryRSS(Feed):
"""RSS feed for the series library."""
ttl = 600
link = '/reader/'
description = 'Updates when a new series is added'
author_name = settings.CONFIG['NAME']
title = f'Library - {author_name}'
item_guid_is_permalink = True
[docs] def items(self) -> Iterable[Series]:
"""
Get an iterable of the feed's items.
:return: An iterable of ``Series`` objects.
"""
categories = Category.objects.only('name')
return Series.objects.only(
'slug', 'title', 'description',
'cover', 'created', 'modified'
).prefetch_related(
Prefetch('categories', queryset=categories)
).order_by('-created')[:_max]
[docs] def item_description(self, item: Series) -> str:
"""
Get the description of the item.
:param item: A ``Series`` object.
:return: The description of the series.
"""
return item.description.replace('\n', '<br/>')
[docs] def item_categories(self, item: Series) -> Iterable[str]:
"""
Get the categories of the item.
:param item: A ``Series`` object.
:return: The names of the series' categories.
"""
return [c.name for c in item.categories.all()]
[docs] def item_pubdate(self, item: Series) -> datetime:
"""
Get the publication date of the item.
:param item: A ``Series`` object.
:return: The date the series was created.
"""
return item.created
[docs] def item_updateddate(self, item: Series) -> datetime:
"""
Get the update date of the item.
:param item: A ``Series`` object.
:return: The date the series was modified.
"""
return item.modified
[docs] def item_enclosure_url(self, item: Series) -> Optional[str]:
"""
Get the enclosure URL of the item.
:param item: A ``Series`` object.
:return: The URL of the series' cover image, if available.
"""
return item.cover.url if item.cover else None
[docs] def item_enclosure_length(self, item: Series) -> Optional[int]:
"""
Get the enclosure length of the item.
:param item: A ``Series`` object.
:return: The size of the series' cover image, if available.
"""
return item.cover.size if item.cover else None
[docs] def item_enclosure_mime_type(self, item: Series) -> Optional[str]:
"""
Get the enclosure type of the item.
:param item: A ``Series`` object.
:return: The mime type of the series' cover image, if available.
"""
return guess_type(item.cover.path)[0] if item.cover else None
[docs]@method_decorator(cache_control(public=True, max_age=600), '__call__')
class LibraryAtom(LibraryRSS):
"""Atom feed for the series library."""
feed_type = Atom1Feed
subtitle = LibraryRSS.description
[docs]@method_decorator(cache_control(public=True, max_age=600), '__call__')
class ReleasesRSS(Feed):
"""RSS feed for chapter releases."""
ttl = 600
author_name = settings.CONFIG['NAME']
item_guid_is_permalink = True
[docs] def get_object(self, request: HttpRequest, slug:
Optional[str] = None) -> Optional[Series]:
"""
Get a ``Series`` object from the request.
:param request: The original request.
:param slug: The slug of the series.
:return: The series that has the given slug,
or ``None`` if the slug is ``None``.
"""
if slug is None:
return None
chapters = Chapter.objects.only(
'title', 'volume', 'number',
'published', 'modified', 'series'
).filter(
published__lte=tz.now(),
series__licensed=False
).order_by(F('volume').asc(nulls_last=True), 'number')
return Series.objects.only(
'slug', 'title', 'licensed', 'format'
).prefetch_related(
Prefetch('chapters', queryset=chapters)
).get(slug=slug)
[docs] def link(self, obj: Optional[Series]) -> str:
"""
Get the link of the feed's page.
:param obj: The object of the feed.
:return: The URL of the series, or the home page.
"""
return obj.get_absolute_url() if obj else '/'
[docs] def title(self, obj: Optional[Series]) -> str:
"""
Get the title of the feed.
:param obj: The object of the feed.
:return: The title of the series, or ``Releases``.
"""
title = obj.title if obj else 'Releases'
return f'{title} - {self.author_name}'
[docs] def description(self, obj: Optional[Series]) -> str:
"""
Get the description of the feed.
:param obj: The object of the feed.
:return: A description with the title of the series, if available.
"""
if obj is None:
return 'Updates when a new chapter is added'
if obj.licensed: # pragma: no cover
return 'This series is licensed.'
return f'Updates when a new chapter of {obj.title} is added'
[docs] def items(self, obj: Optional[Series]) -> Iterable[Chapter]:
"""
Get an iterable of the feed's items.
:param obj: The object of the feed.
:return: An iterable of ``Chapter`` objects.
"""
if getattr(obj, 'licensed', False): # pragma: no cover
return []
if hasattr(obj, 'chapters'):
return list(obj.chapters.all()) # type: ignore
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
).order_by('-published')[:_max]
[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']
url = item.get_absolute_url()[:-1] + '.cbz'
scheme = settings.ACCOUNT_DEFAULT_HTTP_PROTOCOL
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(public=True, max_age=600), '__call__')
class ReleasesAtom(ReleasesRSS):
"""Atom feed for chapter releases."""
feed_type = Atom1Feed
subtitle = ReleasesRSS.description
__all__ = ['LibraryRSS', 'LibraryAtom', 'ReleasesRSS', 'ReleasesAtom']