Ben Sturmfels
956f8c6fda
Each Cast (episode) can belong to one or more Podcast, allowing episodes to be shared between podcasts. This enables us introductory episodes to be delivered in their own feed, but also included in the main "The Corresponding Source" feed. This required adding an additional `podcast_slug` argument to most views. The date archive views were dropped because they're not linked to from anywhere. Added a `podcasts` view as an index of all available Podcasts.
286 lines
12 KiB
Python
286 lines
12 KiB
Python
# Copyright (C) 2008, 2010 Bradley M. Kuhn <bkuhn@ebb.org>
|
|
# Copyright (C) 2006, 2007 Software Freedom Law Center, Inc.
|
|
#
|
|
# This software's license gives you freedom; you can copy, convey,
|
|
# propogate, redistribute and/or modify this program under the terms of
|
|
# the GNU Affero General Public License (AGPL) as published by the Free
|
|
# Software Foundation (FSF), either version 3 of the License, or (at your
|
|
# option) any later version of the AGPL published by the FSF.
|
|
#
|
|
# This program is distributed in the hope that it will be useful, but
|
|
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
|
|
# General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
# along with this program in a file in the toplevel directory called
|
|
# "AGPLv3". If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
|
|
from datetime import datetime
|
|
|
|
from django.contrib.sites.shortcuts import get_current_site
|
|
from django.contrib.syndication.views import Feed, add_domain
|
|
from django.shortcuts import get_object_or_404, render
|
|
from django.urls import reverse
|
|
from django.utils.feedgenerator import Rss201rev2Feed
|
|
|
|
from .models import Cast, Podcast
|
|
from .templatetags.podjango import episode_url
|
|
|
|
# FIXME: Settings here should not be hard-coded for given casts, but
|
|
# should instead have settings from the main screen.
|
|
|
|
|
|
class CastFeedBase(Feed):
|
|
def copyright_holder(self):
|
|
return "Bradley M. Kuhn, Karen M. Sandler"
|
|
|
|
def license_no_html(self):
|
|
return "Licensed under a Creative Commons Attribution-Share Alike 3.0 USA License."
|
|
|
|
def item_copyright(self, item):
|
|
year = 2008
|
|
for attr in ('pub_date', 'date_created', 'date_last_modified'):
|
|
if hasattr(item, attr):
|
|
if hasattr(getattr(item, attr), 'year'):
|
|
year = getattr(getattr(item, attr), 'year')
|
|
break
|
|
return 'Copyright (C) %d, %s. %s' % (year, self.copyright_holder(), self.license_no_html())
|
|
|
|
def item_extra_kwargs(self, item):
|
|
year = 2008
|
|
for attr in ('pub_date', 'date_created', 'date_last_modified'):
|
|
if hasattr(item, attr):
|
|
if hasattr(getattr(item, attr), 'year'):
|
|
year = getattr(getattr(item, attr), 'year')
|
|
break
|
|
return {'year': year}
|
|
|
|
|
|
def for_podcast_feed_extra_kwargs(self, obj):
|
|
return {
|
|
'managingEditorNames': 'Bradley and Karen',
|
|
'rssImage': {'url': 'https://sfconservancy.org/static/podjango/img/cast/tcs_300x300.jpg',
|
|
'width': '144', 'height': '144'},
|
|
'webMaster': 'oggcast@faif.us (Bradley and Karen)',
|
|
'dcCreator': 'oggcast@faif.us (Bradley and Karen)',
|
|
'iTunesExplicit': 'No',
|
|
'iTunesBlock': 'No',
|
|
'iTunesImage': {'url': 'https://sfconservancy.org/static/podjango/img/cast/tcs_300x300.jpg',
|
|
'title': 'The Corresponding Source (formerly Free as in Freedom)',
|
|
'link': self.author_link,
|
|
'type': 'video/jpg'},
|
|
'category': {'name': 'Government & Organizations', 'scheme': 'http://www.itunes.com/dtds/podcast-1.0.dtd',
|
|
'subcats': ['Non-Profit']},
|
|
'keywords': 'open source, opensource, freesoftware, software freedom, legal, law, linux, free, license, gpl, lgpl, agpl, bsd',
|
|
'iTunesAuthor': 'Software Freedom Conservancy',
|
|
'iTunesSubtitle': 'Bi-Weekly Discussion of Legal, Policy, and Any other Issues in the Free, Libre, and Open Source Software (FLOSS) Community',
|
|
'copyrightHolder': self.copyright_holder(),
|
|
'copyrightLicense': self.license_no_html(),
|
|
}
|
|
|
|
|
|
def for_podcast_item_extra_kwargs(self, item):
|
|
return {
|
|
'duration': item.duration,
|
|
'year': item.date_created.year,
|
|
'dcCreator': 'oggcast@faif.us (Bradley and Karen)',
|
|
'intheitembkuhn': item.__dict__.__str__(),
|
|
}
|
|
|
|
|
|
def podcast_helper_add_root_elements(self, handler):
|
|
handler.addQuickElement('managingEditor', self.feed['author_email']
|
|
+ ' (' + self.feed['managingEditorNames'] + ')')
|
|
handler.startElement('image', {})
|
|
handler.addQuickElement('url', self.feed['rssImage']['url'])
|
|
handler.addQuickElement('title', self.feed['author_name'])
|
|
handler.addQuickElement('link', self.feed['link'])
|
|
handler.addQuickElement('width', self.feed['rssImage']['width'])
|
|
handler.addQuickElement('height', self.feed['rssImage']['height'])
|
|
handler.endElement('image')
|
|
|
|
handler.addQuickElement('webMaster', self.feed['webMaster'])
|
|
# handler.addQuickElement('dc:creator', self.feed['dcCreator'])
|
|
handler.addQuickElement('itunes:explicit', self.feed['iTunesExplicit'])
|
|
handler.addQuickElement('itunes:block', self.feed['iTunesBlock'])
|
|
handler.addQuickElement('generator', 'https://sfconservancy.org/')
|
|
|
|
handler.addQuickElement('media:thumbnail', '' , { 'url' : self.feed['rssImage']['url'] })
|
|
handler.addQuickElement('itunes:image', '' , { 'href' : self.feed['iTunesImage']['url']})
|
|
# handler.addQuickElement('itunes:link', '', { 'href' : self.feed['iTunesImage']['url'],
|
|
# 'type' : self.feed['iTunesImage']['type']})
|
|
|
|
handler.addQuickElement("media:category", self.feed['category']['name'],
|
|
{'scheme': self.feed['category']['scheme']})
|
|
if not (self.feed['category']['subcats'] and len(self.feed['category']['subcats']) > 0):
|
|
handler.addQuickElement("itunes:category", '', {'text': self.feed['category']['name']})
|
|
else:
|
|
handler.startElement("itunes:category", {'text': self.feed['category']['name']})
|
|
for cc in self.feed['category']['subcats']:
|
|
handler.addQuickElement("itunes:category", '', {'text': cc})
|
|
handler.endElement("itunes:category")
|
|
|
|
handler.addQuickElement("media:keywords", self.feed['keywords'].replace(" ", ","))
|
|
|
|
handler.startElement("itunes:owner", {})
|
|
handler.addQuickElement("itunes:email", self.feed['author_email'])
|
|
handler.addQuickElement("itunes:name", self.feed['author_name'])
|
|
handler.endElement("itunes:owner")
|
|
|
|
handler.addQuickElement("itunes:summary", self.feed['description'])
|
|
handler.addQuickElement("itunes:subtitle", self.feed['iTunesSubtitle'])
|
|
|
|
handler.addQuickElement("itunes:author", self.feed['iTunesAuthor'])
|
|
handler.addQuickElement('atom:link', '', {'rel': "self", 'href': self.feed['feed_url'],
|
|
'type': "application/rss+xml"})
|
|
|
|
years = {}
|
|
for ii in self.items:
|
|
years[ii['year']] = 1
|
|
|
|
copyrightString = ""
|
|
ll = years.keys()
|
|
sorted(ll)
|
|
for yy in ll:
|
|
copyrightString += "%d, " % yy
|
|
copyrightString += "%s. %s" % (self.feed['copyrightHolder'], self.feed['copyrightLicense'])
|
|
|
|
handler.addQuickElement('copyright', copyrightString)
|
|
handler.addQuickElement('media:copyright', "Copyright (C) " + copyrightString)
|
|
|
|
|
|
def podcast_helper_add_item_elements(self, handler, item):
|
|
handler.addQuickElement("itunes:explicit", self.feed['iTunesExplicit'])
|
|
handler.addQuickElement("itunes:block", self.feed['iTunesBlock'])
|
|
handler.addQuickElement("itunes:keywords", self.feed['keywords'])
|
|
# handler.addQuickElement('dc:creator', self.feed['dcCreator'])
|
|
handler.addQuickElement("itunes:author", item['author_name'])
|
|
handler.addQuickElement("itunes:duration", item['duration'])
|
|
if 'enclosure' in item:
|
|
handler.addQuickElement('media:content', '', {'url': item['enclosure'].url,
|
|
'fileSize': item['enclosure'].length,
|
|
'type': item['enclosure'].mime_type})
|
|
|
|
|
|
# http://www.feedforall.com/itune-tutorial-tags.htm
|
|
# http://www.feedforall.com/mediarss.htm
|
|
class iTunesFeedType(Rss201rev2Feed):
|
|
def root_attributes(self):
|
|
attrs = super().root_attributes()
|
|
attrs['xmlns:itunes'] = 'http://www.itunes.com/dtds/podcast-1.0.dtd'
|
|
attrs['xmlns:atom'] = 'http://www.w3.org/2005/Atom'
|
|
attrs['xmlns:media'] = 'http://search.yahoo.com/mrss/'
|
|
# attrs['xmlns:dc'] = "http://purl.org/dc/elements/1.1/"
|
|
return attrs
|
|
|
|
def add_root_elements(self, handler):
|
|
super().add_root_elements(handler)
|
|
podcast_helper_add_root_elements(self, handler)
|
|
|
|
def add_item_elements(self, handler, item):
|
|
super().add_item_elements(handler, item)
|
|
podcast_helper_add_item_elements(self, handler, item)
|
|
|
|
|
|
class CastFeed(CastFeedBase):
|
|
feed_type = iTunesFeedType
|
|
title = "The Corresponding Source (formerly Free as in Freedom)"
|
|
link = "/cast/"
|
|
description = "A bi-weekly discussion of legal, policy, and other issues in the open source and software freedom community (including occasional interviews) from Brooklyn, New York, USA. Presented by Karen Sandler and Bradley M. Kuhn."
|
|
author_email = "podcast@faif.us"
|
|
author_link = "https://sfconservancy.org/"
|
|
author_name = "Software Freedom Conservancy"
|
|
title_template = "feeds/podcast_title.html"
|
|
description_template = "feeds/podcast_description.html"
|
|
|
|
def get_feed(self, obj, request):
|
|
# Enclosure (media) URLs don't automatically get the protocol and
|
|
# domain, but these are required for the podcast to work
|
|
# properly. added. This provides current_site and is_secure to the feed
|
|
# so that we have it in context for use in `item_enclosure_url`.
|
|
self.current_site = get_current_site(request)
|
|
self.is_secure = request.is_secure()
|
|
return super().get_feed(obj, request)
|
|
|
|
def get_object(self, request, podcast_slug):
|
|
self.podcast = Podcast.objects.get(slug=podcast_slug)
|
|
return self.podcast
|
|
|
|
def items(self, obj):
|
|
return Cast.objects.filter(podcast=obj, pub_date__lte=datetime.now()).order_by('-pub_date')
|
|
|
|
def item_pubdate(self, item):
|
|
return item.pub_date
|
|
|
|
def item_link(self, item):
|
|
return episode_url(self.podcast, item)
|
|
|
|
def item_author_email(self, obj):
|
|
return "oggcast@faif.us"
|
|
|
|
def item_author_name(self, obj):
|
|
return "Software Freedom Conservancy"
|
|
|
|
def item_author_link(self, obj):
|
|
return "https://sfconservancy.org"
|
|
|
|
def item_categories(self, item):
|
|
return ('Technology',)
|
|
|
|
def copyright_holder(self):
|
|
return "Software Freedom Conservancy"
|
|
|
|
def license_no_html(self):
|
|
return "Licensed under a Creative Commons Attribution-Share Alike 3.0 USA License."
|
|
|
|
def feed_extra_kwargs(self, obj):
|
|
return for_podcast_feed_extra_kwargs(self, obj)
|
|
|
|
def item_extra_kwargs(self, item):
|
|
return for_podcast_item_extra_kwargs(self, item)
|
|
|
|
# FIXME:
|
|
# GUEST NAME GOES HERE!!!
|
|
# <itunes:author>
|
|
# If applicable, at the item level, this tag can contain information
|
|
# about the person(s) featured on a specific episode.
|
|
|
|
|
|
class Mp3CastFeed(CastFeed):
|
|
def item_enclosure_mime_type(self):
|
|
return "audio/mpeg"
|
|
|
|
def item_enclosure_url(self, item):
|
|
return add_domain(self.current_site.domain, item.mp3_path, self.is_secure)
|
|
|
|
def item_enclosure_length(self, item):
|
|
return item.mp3_length
|
|
|
|
|
|
class OggCastFeed(CastFeed):
|
|
def item_enclosure_mime_type(self):
|
|
return "audio/ogg"
|
|
|
|
def item_enclosure_url(self, item):
|
|
return add_domain(self.current_site.domain, item.ogg_path, self.is_secure)
|
|
|
|
def item_enclosure_length(self, item):
|
|
return item.ogg_length
|
|
|
|
|
|
def view(request, podcast_slug):
|
|
"""Listing of all available feeds
|
|
"""
|
|
feed_dict = {
|
|
'feed-ogg': OggCastFeed,
|
|
'feed-mp3': Mp3CastFeed,
|
|
}
|
|
podcast = get_object_or_404(Podcast, slug=podcast_slug)
|
|
# make each feed know its canonical url
|
|
for k, v in feed_dict.items():
|
|
v.get_absolute_url = reverse(f'podjango:{k}', kwargs={'podcast_slug': podcast.slug})
|
|
feeds = feed_dict.values()
|
|
return render(request, "feeds.html", {'podcast': podcast,
|
|
'feeds': feeds})
|