evorepo/feeds.py

176 lines
6.1 KiB
Python
Raw Normal View History

2009-10-30 14:24:26 -07:00
import datetime
from decimal import Decimal, ROUND_HALF_DOWN
from django.contrib.syndication.views import Feed
from django.core.cache import cache
from django.db.models import Q
from django.db.models.signals import post_save
from django.utils.hashcompat import md5_constructor
from django.views.decorators.http import condition
from main.models import Arch, Repo, Package
from news.models import News
2009-10-30 14:24:26 -07:00
CACHE_TIMEOUT = 1800
INVALIDATE_TIMEOUT = 15
CACHE_PACKAGE_KEY = 'cache_package_latest'
CACHE_NEWS_KEY = 'cache_news_latest'
def utc_offset():
'''Calculate the UTC offset from local time. Useful for converting values
stored in local time to things like cache last modifed headers.'''
timediff = datetime.datetime.utcnow() - datetime.datetime.now()
secs = timediff.days * 86400 + timediff.seconds
# round to nearest minute
mins = Decimal(secs) / Decimal(60)
mins = mins.quantize(Decimal('0'), rounding=ROUND_HALF_DOWN)
return datetime.timedelta(minutes=int(mins))
def retrieve_package_latest():
# we could break this down based on the request url, but it would probably
# cost us more in query time to do so.
latest = cache.get(CACHE_PACKAGE_KEY)
if latest:
return latest
try:
latest = Package.objects.values('last_update').latest(
'last_update')['last_update']
latest = latest + utc_offset()
# Using add means "don't overwrite anything in there". What could be in
# there is an explicit None value that our refresh signal set, which
# means we want to avoid race condition possibilities for a bit.
cache.add(CACHE_PACKAGE_KEY, latest, CACHE_TIMEOUT)
return latest
except Package.DoesNotExist:
pass
return None
def refresh_package_latest(**kwargs):
# We could delete the value, but that could open a race condition
# where the new data wouldn't have been committed yet by the calling
# thread. Instead, explicitly set it to None for a short amount of time.
# Hopefully by the time it expires we will have committed, and the cache
# will be valid again. See "Scaling Django" by Mike Malone, slide 30.
cache.set(CACHE_PACKAGE_KEY, None, INVALIDATE_TIMEOUT)
def package_etag(request, *args, **kwargs):
latest = retrieve_package_latest()
if latest:
return md5_constructor(str(kwargs) + str(latest)).hexdigest()
return None
def package_last_modified(request, *args, **kwargs):
return retrieve_package_latest()
2009-10-30 14:24:26 -07:00
class PackageFeed(Feed):
link = '/packages/'
title_template = 'feeds/packages_title.html'
description_template = 'feeds/packages_description.html'
def __call__(self, request, *args, **kwargs):
wrapper = condition(etag_func=package_etag, last_modified_func=package_last_modified)
return wrapper(super(PackageFeed, self).__call__)(request, *args, **kwargs)
def get_object(self, request, arch='', repo=''):
obj = dict()
qs = Package.objects.select_related('arch', 'repo').order_by('-last_update')
if arch != '':
# feed for a single arch, also include 'any' packages everywhere
a = Arch.objects.get(name=arch)
qs = qs.filter(Q(arch=a) | Q(arch__agnostic=True))
obj['arch'] = a
if repo != '':
# feed for a single arch AND repo
r = Repo.objects.get(name=repo)
qs = qs.filter(repo=r)
obj['repo'] = r
obj['qs'] = qs[:50]
return obj
def title(self, obj):
s = 'Arch Linux: Recent package updates'
if 'repo' in obj:
s += ' (%s [%s])' % (obj['arch'].name, obj['repo'].name.lower())
elif 'arch' in obj:
s += ' (%s)' % (obj['arch'].name)
return s
def description(self, obj):
s = 'Recently updated packages in the Arch Linux package repositories'
if 'arch' in obj:
s += ' for the \'%s\' architecture' % obj['arch'].name.lower()
if not obj['arch'].agnostic:
s += ' (including \'any\' packages)'
if 'repo' in obj:
s += ' in the [%s] repository' % obj['repo'].name.lower()
s += '.'
return s
def items(self, obj):
return obj['qs']
2009-10-30 14:24:26 -07:00
def item_pubdate(self, item):
return item.last_update
def item_categories(self, item):
return (item.repo.name, item.arch.name)
2009-10-30 14:24:26 -07:00
def retrieve_news_latest():
latest = cache.get(CACHE_NEWS_KEY)
if latest:
return latest
try:
latest = News.objects.values('last_modified').latest(
'last_modified')['last_modified']
latest = latest + utc_offset()
# same thoughts apply as in retrieve_package_latest
cache.add(CACHE_NEWS_KEY, latest, CACHE_TIMEOUT)
return latest
except News.DoesNotExist:
pass
return None
def refresh_news_latest(**kwargs):
# same thoughts apply as in refresh_package_latest
cache.set(CACHE_NEWS_KEY, None, INVALIDATE_TIMEOUT)
def news_etag(request, *args, **kwargs):
latest = retrieve_news_latest()
if latest:
return md5_constructor(str(latest)).hexdigest()
return None
def news_last_modified(request, *args, **kwargs):
return retrieve_news_latest()
2009-10-30 14:24:26 -07:00
class NewsFeed(Feed):
title = 'Arch Linux: Recent news updates'
link = '/news/'
description = 'The latest and greatest news from the Arch Linux distribution.'
title_template = 'feeds/news_title.html'
description_template = 'feeds/news_description.html'
2009-10-30 14:24:26 -07:00
def __call__(self, request, *args, **kwargs):
wrapper = condition(etag_func=news_etag, last_modified_func=news_last_modified)
return wrapper(super(NewsFeed, self).__call__)(request, *args, **kwargs)
2009-10-30 14:24:26 -07:00
def items(self):
return News.objects.select_related('author').order_by('-postdate', '-id')[:10]
2009-10-30 14:24:26 -07:00
def item_pubdate(self, item):
d = item.postdate
return datetime.datetime(d.year, d.month, d.day)
def item_author_name(self, item):
return item.author.get_full_name()
# connect signals needed to keep cache in line with reality
post_save.connect(refresh_package_latest, sender=Package)
post_save.connect(refresh_news_latest, sender=News)
2009-10-30 14:24:26 -07:00
# vim: set ts=4 sw=4 et: