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

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.""" 

11 

12import re 

13from itertools import chain 

14 

15from django.conf import settings 

16from django.contrib.syndication.views import Feed 

17from django.http import Http404 

18from django.utils.html import strip_tags 

19 

20from distro_tracker.core.models import ( 

21 ActionItem, 

22 News, 

23 NewsRenderer, 

24 get_web_package 

25) 

26 

27 

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 

36 

37 

38class PackageNewsFeed(Feed): 

39 _DEFAULT_LIMIT = 30 

40 

41 def get_object(self, request, package_name): 

42 package = get_web_package(package_name) 

43 if package is None: 

44 raise Http404 

45 

46 return package 

47 

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) 

53 

54 @filter_control_chars 

55 def link(self, obj): 

56 return obj.get_absolute_url() 

57 

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 ) 

64 

65 def items(self, obj): 

66 item_limit = getattr(settings, 'DISTRO_TRACKER_RSS_ITEM_LIMIT', 

67 self._DEFAULT_LIMIT) 

68 

69 news = obj.news_set.all() 

70 action_items = obj.action_items.all() 

71 

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 

77 

78 all_items = chain(news, action_items) 

79 return sorted(all_items, key=item_key, reverse=True)[:item_limit] 

80 

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) 

87 

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) 

97 

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 

101 

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