feeds.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. from datetime import datetime, time
  2. from pytz import utc
  3. from django.conf import settings
  4. from django.contrib.sites.models import Site
  5. from django.contrib.syndication.views import Feed
  6. from django.db import connection
  7. from django.db.models import Q
  8. from django.utils.feedgenerator import Rss201rev2Feed
  9. from django.views.decorators.http import condition
  10. from main.models import Arch, Repo, Package
  11. from news.models import News
  12. from releng.models import Release
  13. class BatchWritesWrapper(object):
  14. def __init__(self, outfile):
  15. self.outfile = outfile
  16. self.buf = []
  17. def write(self, s):
  18. buf = self.buf
  19. buf.append(s)
  20. if len(buf) >= 40:
  21. self.outfile.write(''.join(buf))
  22. self.buf = []
  23. def flush(self):
  24. self.outfile.write(''.join(self.buf))
  25. self.outfile.flush()
  26. class FasterRssFeed(Rss201rev2Feed):
  27. def write(self, outfile, encoding):
  28. '''
  29. Batch the underlying 'write' calls on the outfile because Python's
  30. default saxutils XmlGenerator is a POS that insists on unbuffered
  31. write/flush calls. This sucks when it is making 1-byte calls to write
  32. '>' closing tags and over 1600 write calls in our package feed.
  33. '''
  34. wrapper = BatchWritesWrapper(outfile)
  35. super(FasterRssFeed, self).write(wrapper, encoding)
  36. wrapper.flush()
  37. def package_last_modified(request, *args, **kwargs):
  38. cursor = connection.cursor()
  39. cursor.execute("SELECT MAX(last_update) FROM packages")
  40. return cursor.fetchone()[0]
  41. class PackageFeed(Feed):
  42. feed_type = FasterRssFeed
  43. link = '/packages/'
  44. def __call__(self, request, *args, **kwargs):
  45. wrapper = condition(last_modified_func=package_last_modified)
  46. return wrapper(super(PackageFeed, self).__call__)(request, *args, **kwargs)
  47. __name__ = 'package_feed'
  48. def get_object(self, request, arch='', repo=''):
  49. obj = dict()
  50. qs = Package.objects.normal().order_by('-last_update')
  51. if arch != '':
  52. # feed for a single arch, also include 'any' packages everywhere
  53. a = Arch.objects.get(name=arch)
  54. qs = qs.filter(Q(arch=a) | Q(arch__agnostic=True))
  55. obj['arch'] = a
  56. if repo != '':
  57. # feed for a single arch AND repo
  58. r = Repo.objects.get(name__iexact=repo)
  59. qs = qs.filter(repo=r)
  60. obj['repo'] = r
  61. else:
  62. qs = qs.filter(repo__staging=False)
  63. obj['qs'] = qs[:50]
  64. return obj
  65. def title(self, obj):
  66. s = settings.BRANDING_DISTRONAME+': Recent package updates'
  67. if 'repo' in obj and 'arch' in obj:
  68. s += ' (%s [%s])' % (obj['arch'].name, obj['repo'].name.lower())
  69. elif 'repo' in obj:
  70. s += ' [%s]' % (obj['repo'].name.lower())
  71. elif 'arch' in obj:
  72. s += ' (%s)' % (obj['arch'].name)
  73. return s
  74. def description(self, obj):
  75. s = 'Recently updated packages in the '+settings.BRANDING_DISTRONAME+' package repositories'
  76. if 'arch' in obj:
  77. s += ' for the \'%s\' architecture' % obj['arch'].name.lower()
  78. if not obj['arch'].agnostic:
  79. s += ' (including \'any\' packages)'
  80. if 'repo' in obj:
  81. s += ' in the [%s] repository' % obj['repo'].name.lower()
  82. s += '.'
  83. return s
  84. subtitle = description
  85. def items(self, obj):
  86. return obj['qs']
  87. item_guid_is_permalink = False
  88. def item_guid(self, item):
  89. # http://diveintomark.org/archives/2004/05/28/howto-atom-id
  90. date = item.last_update
  91. return 'tag:%s,%s:%s%s' % (Site.objects.get_current().domain,
  92. date.strftime('%Y-%m-%d'), item.get_absolute_url(),
  93. date.strftime('%Y%m%d%H%M'))
  94. def item_pubdate(self, item):
  95. return item.last_update
  96. def item_title(self, item):
  97. return '%s %s %s' % (item.pkgname, item.full_version, item.arch.name)
  98. def item_description(self, item):
  99. return item.pkgdesc
  100. def item_categories(self, item):
  101. return (item.repo.name, item.arch.name)
  102. def news_last_modified(request, *args, **kwargs):
  103. cursor = connection.cursor()
  104. cursor.execute("SELECT MAX(last_modified) FROM news")
  105. return cursor.fetchone()[0]
  106. class NewsFeed(Feed):
  107. feed_type = FasterRssFeed
  108. title = settings.BRANDING_DISTRONAME+': Recent news updates'
  109. link = '/news/'
  110. description = 'The latest and greatest news from the '+settings.BRANDING_DISTRONAME+' distribution.'
  111. subtitle = description
  112. def __call__(self, request, *args, **kwargs):
  113. wrapper = condition(last_modified_func=news_last_modified)
  114. return wrapper(super(NewsFeed, self).__call__)(request, *args, **kwargs)
  115. __name__ = 'news_feed'
  116. def items(self):
  117. return News.objects.select_related('author').order_by(
  118. '-postdate', '-id')[:10]
  119. item_guid_is_permalink = False
  120. def item_guid(self, item):
  121. return item.guid
  122. def item_pubdate(self, item):
  123. return item.postdate
  124. def item_updateddate(self, item):
  125. return item.last_modified
  126. def item_author_name(self, item):
  127. return item.author.get_full_name()
  128. def item_title(self, item):
  129. return item.title
  130. def item_description(self, item):
  131. return item.html()
  132. class ReleaseFeed(Feed):
  133. feed_type = FasterRssFeed
  134. title = settings.BRANDING_DISTRONAME+': Releases'
  135. link = '/download/'
  136. description = 'Release ISOs'
  137. subtitle = description
  138. __name__ = 'release_feed'
  139. def items(self):
  140. return Release.objects.filter(available=True)[:10]
  141. def item_title(self, item):
  142. return item.version
  143. def item_description(self, item):
  144. return item.info_html()
  145. def item_pubdate(self, item):
  146. return datetime.combine(item.release_date, time()).replace(tzinfo=utc)
  147. def item_updateddate(self, item):
  148. return item.last_modified
  149. item_guid_is_permalink = False
  150. def item_guid(self, item):
  151. # http://diveintomark.org/archives/2004/05/28/howto-atom-id
  152. date = item.release_date
  153. return 'tag:%s,%s:%s' % (Site.objects.get_current().domain,
  154. date.strftime('%Y-%m-%d'), item.get_absolute_url())
  155. def item_enclosure_url(self, item):
  156. domain = Site.objects.get_current().domain
  157. proto = 'https'
  158. return "%s://%s/%s.torrent" % (proto, domain, item.iso_url())
  159. def item_enclosure_length(self, item):
  160. if item.torrent_data:
  161. torrent = item.torrent()
  162. return torrent['file_length'] or ""
  163. return ""
  164. item_enclosure_mime_type = 'application/x-bittorrent'
  165. # vim: set ts=4 sw=4 et: