Coverage for distro_tracker/core/news_feed.py: 93%
61 statements
« prev ^ index » next coverage.py v6.5.0, created at 2026-11-07 18:35 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2026-11-07 18:35 +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
18from django.utils.html import strip_tags
20from distro_tracker.core.models import (
21 ActionItem,
22 News,
23 NewsRenderer,
24 get_web_package
25)
28def filter_control_chars(method):
29 """Filter out undesirable control characters."""
30 # We have to filter out control chars otherwise the FeedGenerator
31 # raises UnserializableContentError (see django/utils/xmlutils.py)
32 def wrapped(self, obj):
33 result = method(self, obj)
34 return re.sub(r'[\x00-\x08\x0B-\x0C\x0E-\x1F]', '', result)
35 return wrapped
38class PackageNewsFeed(Feed):
39 _DEFAULT_LIMIT = 30
41 def get_object(self, request, package_name):
42 package = get_web_package(package_name)
43 if package is None:
44 raise Http404
46 return package
48 @filter_control_chars
49 def title(self, obj):
50 return "{vendor} package news for {pkg}".format(
51 vendor=settings.DISTRO_TRACKER_VENDOR_NAME,
52 pkg=obj.name)
54 @filter_control_chars
55 def link(self, obj):
56 return obj.get_absolute_url()
58 @filter_control_chars
59 def description(self, obj):
60 return (
61 "Latest developer's news for {vendor} source package {pkg}"
62 .format(vendor=settings.DISTRO_TRACKER_VENDOR_NAME, pkg=obj.name)
63 )
65 def items(self, obj):
66 item_limit = getattr(settings, 'DISTRO_TRACKER_RSS_ITEM_LIMIT',
67 self._DEFAULT_LIMIT)
69 news = obj.news_set.all()
70 action_items = obj.action_items.all()
72 def item_key(item):
73 if isinstance(item, ActionItem):
74 return item.last_updated_timestamp
75 elif isinstance(item, News): 75 ↛ exitline 75 didn't return from function 'item_key', because the condition on line 75 was never false
76 return item.datetime_created
78 all_items = chain(news, action_items)
79 return sorted(all_items, key=item_key, reverse=True)[:item_limit]
81 @filter_control_chars
82 def item_title(self, item):
83 if isinstance(item, News):
84 return item.title
85 elif isinstance(item, ActionItem): 85 ↛ exitline 85 didn't return from function 'item_title', because the condition on line 85 was never false
86 return strip_tags(item.short_description)
88 @filter_control_chars
89 def item_description(self, item):
90 if isinstance(item, News):
91 renderer_class = NewsRenderer.get_renderer_for_content_type(
92 item.content_type)
93 if renderer_class is None: 93 ↛ 94line 93 didn't jump to line 94, because the condition on line 93 was never true
94 renderer_class = NewsRenderer.get_renderer_for_content_type(
95 'text/plain')
96 renderer = renderer_class(item)
98 return renderer.render_to_string()
99 elif isinstance(item, ActionItem): 99 ↛ exitline 99 didn't return from function 'item_description', because the condition on line 99 was never false
100 return item.full_description
102 def item_pubdate(self, item):
103 if isinstance(item, ActionItem):
104 return item.last_updated_timestamp
105 elif isinstance(item, News): 105 ↛ exitline 105 didn't return from function 'item_pubdate', because the condition on line 105 was never false
106 return item.datetime_created