Coverage for distro_tracker/core/news_feed.py: 93%
60 statements
« prev ^ index » next coverage.py v6.5.0, created at 2025-01-12 09:15 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2025-01-12 09:15 +0000
1# Copyright 2013-2018 The Distro Tracker Developers
2# See the COPYRIGHT file at the top-level directory of this distribution and
3# at https://deb.li/DTAuthors
4#
5# This file is part of Distro Tracker. It is subject to the license terms
6# in the LICENSE file found in the top-level directory of this
7# distribution and at https://deb.li/DTLicense. No part of Distro Tracker,
8# including this file, may be copied, modified, propagated, or distributed
9# except according to the terms contained in the LICENSE file.
10"""Implements the RSS news feed."""
12import re
13from itertools import chain
15from django.conf import settings
16from django.contrib.syndication.views import Feed
17from django.http import Http404
19from distro_tracker.core.models import (
20 ActionItem,
21 News,
22 NewsRenderer,
23 get_web_package
24)
27def filter_control_chars(method):
28 """Filter out undesirable control characters."""
29 # We have to filter out control chars otherwise the FeedGenerator
30 # raises UnserializableContentError (see django/utils/xmlutils.py)
31 def wrapped(self, obj):
32 result = method(self, obj)
33 return re.sub(r'[\x00-\x08\x0B-\x0C\x0E-\x1F]', '', result)
34 return wrapped
37class PackageNewsFeed(Feed):
38 _DEFAULT_LIMIT = 30
40 def get_object(self, request, package_name):
41 package = get_web_package(package_name)
42 if package is None:
43 raise Http404
45 return package
47 @filter_control_chars
48 def title(self, obj):
49 return "{vendor} package news for {pkg}".format(
50 vendor=settings.DISTRO_TRACKER_VENDOR_NAME,
51 pkg=obj.name)
53 @filter_control_chars
54 def link(self, obj):
55 return obj.get_absolute_url()
57 @filter_control_chars
58 def description(self, obj):
59 return (
60 "Latest developer's news for {vendor} source package {pkg}"
61 .format(vendor=settings.DISTRO_TRACKER_VENDOR_NAME, pkg=obj.name)
62 )
64 def items(self, obj):
65 item_limit = getattr(settings, 'DISTRO_TRACKER_RSS_ITEM_LIMIT',
66 self._DEFAULT_LIMIT)
68 news = obj.news_set.all()
69 action_items = obj.action_items.all()
71 def item_key(item):
72 if isinstance(item, ActionItem):
73 return item.last_updated_timestamp
74 elif isinstance(item, News): 74 ↛ exitline 74 didn't return from function 'item_key', because the condition on line 74 was never false
75 return item.datetime_created
77 all_items = chain(news, action_items)
78 return sorted(all_items, key=item_key, reverse=True)[:item_limit]
80 @filter_control_chars
81 def item_title(self, item):
82 if isinstance(item, News):
83 return item.title
84 elif isinstance(item, ActionItem): 84 ↛ exitline 84 didn't return from function 'item_title', because the condition on line 84 was never false
85 return item.short_description
87 @filter_control_chars
88 def item_description(self, item):
89 if isinstance(item, News):
90 renderer_class = NewsRenderer.get_renderer_for_content_type(
91 item.content_type)
92 if renderer_class is None: 92 ↛ 93line 92 didn't jump to line 93, because the condition on line 92 was never true
93 renderer_class = NewsRenderer.get_renderer_for_content_type(
94 'text/plain')
95 renderer = renderer_class(item)
97 return renderer.render_to_string()
98 elif isinstance(item, ActionItem): 98 ↛ exitline 98 didn't return from function 'item_description', because the condition on line 98 was never false
99 return item.full_description
101 def item_pubdate(self, item):
102 if isinstance(item, ActionItem):
103 return item.last_updated_timestamp
104 elif isinstance(item, News): 104 ↛ exitline 104 didn't return from function 'item_pubdate', because the condition on line 104 was never false
105 return item.datetime_created