Corrected address and added names.
[podjango:podjango.git] / podjango / feeds.py
1 #  Copyright (C) 2008, 2010 Bradley M. Kuhn <bkuhn@ebb.org>
2 #  Copyright (C) 2006, 2007 Software Freedom Law Center, Inc.
3 #
4 # This software's license gives you freedom; you can copy, convey,
5 # propogate, redistribute and/or modify this program under the terms of
6 # the GNU Affero General Public License (AGPL) as published by the Free
7 # Software Foundation (FSF), either version 3 of the License, or (at your
8 # option) any later version of the AGPL published by the FSF.
9 #
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero
13 # General Public License for more details.
14 #
15 # You should have received a copy of the GNU Affero General Public License
16 # along with this program in a file in the toplevel directory called
17 # "AGPLv3".  If not, see <http://www.gnu.org/licenses/>.
18 #
19
20 from django.contrib.syndication.feeds import Feed
21 from django.utils.feedgenerator import Rss201rev2Feed 
22 #from podjango.apps.staff.models import Person
23 from podjango.apps.cast.models import Cast
24
25 from django.shortcuts import render_to_response
26 from django.conf import settings
27 from datetime import datetime
28
29 import itertools
30 import operator
31
32 # FIXME: Settings here should not be hard-coded for given casts, but
33 # should instead have settings from the main screen.
34
35 class CastFeedBase(Feed):
36     def copyright_holder(self): return "Bradley M. Kuhn, Karen M. Sandler"
37
38     def license_no_html(self): return "Licensed under a Creative Commons Attribution-No Derivative Works 3.0 United States License."
39
40     def item_copyright(self, item):
41         year = 2008
42         for attr in ('pub_date', 'date_created', 'date_last_modified'):
43             if hasattr(item, attr):
44                 if hasattr(getattr(item, attr), 'year'):
45                     year = getattr(getattr(item, attr), 'year')
46                     break
47         return 'Copyright (C) %d, %s.  %s' % (year, self.copyright_holder(), self.license_no_html())
48
49     def item_extra_kwargs(self, item):
50         year = 2008
51         for attr in ('pub_date', 'date_created', 'date_last_modified'):
52             if hasattr(item, attr):
53                 if hasattr(getattr(item, attr), 'year'):
54                     year = getattr(getattr(item, attr), 'year')
55                     break
56         return { 'year' : year }
57
58 def for_podcast_feed_extra_kwargs(self, obj):
59     return { 'managingEditorNames' : 'Bradley and Karen',
60              'rssImage' : { 'url' : 'http://faif.us/img/cast/faif_200x200.jpg',
61                             'width' : '200', 'height' : '200' },
62              'webMaster' : 'oggcast@faif.us (Bradley and Karen)',
63              'dcCreator' : 'oggcast@faif.us (Bradley and Karen)',
64              'iTunesExplicit'  : 'No',
65              'iTunesBlock' : 'No',
66              'iTunesImage' : { 'url' : 'http://faif.us/img/cast/faif_300x300.jpg',
67                                'title' : 'Free as in Freedom',
68                                'link' : self.author_link,
69                                'type' : 'video/jpg'},
70              'category' : { 'name' : 'Technology', 'scheme' : 'http://www.itunes.com/dtds/podcast-1.0.dtd',
71                             'subcats' : [ 'Computers', 'Information Technology', 'Operating Systems' ] },
72              'keywords' : 'open source, opensource, freesoftware, software freedom, legal, law, linux, free, license, gpl, lgpl, agpl, bsd',
73              'iTunesAuthor' : 'Free as in Freedom',
74              'iTunesSubtitle' : 'Bi-Weekly Discussion of Legal, Policy, and Any other Issues in the Free, Libre, and Open Source Software (FLOSS) Community',
75              'copyrightHolder' : self.copyright_holder(),
76              'copyrightLicense' : self.license_no_html() }
77
78 def for_podcast_item_extra_kwargs(self, item):
79     return { 'duration' : item.duration,
80              'year' : item.date_created.year,
81              'dcCreator' : 'oggcast@faif.us (Bradley and Karen)',
82              'intheitembkuhn' : item.__dict__.__str__()}
83
84 def podcast_helper_add_root_elements(self, handler):
85     handler.addQuickElement('managingEditor', self.feed['author_email'] 
86                             + ' (' + self.feed['managingEditorNames'] + ')')
87     handler.startElement('image', {})
88     handler.addQuickElement('url', self.feed['rssImage']['url'])
89     handler.addQuickElement('title', self.feed['author_name'])
90     handler.addQuickElement('link', self.feed['link'])
91     handler.addQuickElement('width', self.feed['rssImage']['width'])
92     handler.addQuickElement('height', self.feed['rssImage']['height'])
93     handler.endElement('image')
94     
95     handler.addQuickElement('webMaster', self.feed['webMaster'])
96     handler.addQuickElement('dc:creator', self.feed['dcCreator'])
97     handler.addQuickElement('itunes:explicit', self.feed['iTunesExplicit'])
98     handler.addQuickElement('itunes:block', self.feed['iTunesBlock'])
99     handler.addQuickElement('generator', 'http://www.faif.us/code')
100     
101     handler.addQuickElement('media:thumbnail', '' , { 'url' : self.feed['rssImage']['url'] })
102     handler.addQuickElement('itunes:image', '' , { 'href' : self.feed['iTunesImage']['url']})
103     handler.addQuickElement('itunes:link', '', { 'href' : self.feed['iTunesImage']['url'],
104                                                  'type' : self.feed['iTunesImage']['type']})
105     
106     handler.addQuickElement(u"media:category", self.feed['category']['name'],
107                             { 'scheme': self.feed['category']['scheme']})
108     if not (self.feed['category']['subcats'] and len(self.feed['category']['subcats']) > 0): 
109         handler.addQuickElement(u"itunes:category", '', { 'text': self.feed['category']['name']})
110     else:
111         handler.startElement(u"itunes:category", { 'text': self.feed['category']['name']})
112         for cc in self.feed['category']['subcats']:
113             handler.addQuickElement(u"itunes:category", '', { 'text': cc })
114         handler.endElement(u"itunes:category")
115
116     handler.addQuickElement(u"media:keywords", self.feed['keywords'].replace(" ", ","))
117     
118     handler.startElement(u"itunes:owner", {})
119     handler.addQuickElement(u"itunes:email", self.feed['author_email'])
120     handler.addQuickElement(u"itunes:name", self.feed['author_name'])
121     handler.endElement(u"itunes:owner")
122     
123     handler.addQuickElement(u"itunes:summary", self.feed['description'])
124     handler.addQuickElement(u"itunes:subtitle", self.feed['iTunesSubtitle'])
125     
126     handler.addQuickElement(u"itunes:author", self.feed['iTunesAuthor'])
127     handler.addQuickElement(u'atom:link', '', { 'rel' : "self",  'href'  : self.feed['feed_url'],
128                                                 'type' : "application/rss+xml"})
129     
130     years = {}
131     for ii in self.items: years[ii['year']] = 1
132     
133     copyrightString = ""
134     ll = years.keys()
135     ll.sort()
136     for yy in ll: copyrightString += "%d, " % yy 
137     copyrightString += "%s.  %s" % (self.feed['copyrightHolder'], self.feed['copyrightLicense'])
138     
139     handler.addQuickElement('copyright', copyrightString)
140     handler.addQuickElement('media:copyright', "Copyright (C) " + copyrightString)
141     
142 def podcast_helper_add_item_elements(self, handler, item):
143     handler.addQuickElement("itunes:explicit", self.feed['iTunesExplicit'])
144     handler.addQuickElement("itunes:block", self.feed['iTunesBlock'])
145     handler.addQuickElement(u"itunes:keywords", self.feed['keywords'])
146     handler.addQuickElement('dc:creator', self.feed['dcCreator'])
147     handler.addQuickElement("itunes:author", item['author_name'])
148     handler.addQuickElement("itunes:duration", item['duration'])
149     handler.addQuickElement('media:content', '', { 'url' : item['enclosure'].url,
150                                                    'fileSize' : item['enclosure'].length,
151                                                    'type' : item['enclosure'].mime_type})
152
153 class OmnibusFeedType(Rss201rev2Feed):
154     def root_attributes(self):
155         attrs = super(OmnibusFeedType, self).root_attributes()
156         attrs['xmlns:itunes'] = 'http://www.itunes.com/dtds/podcast-1.0.dtd'
157         attrs['xmlns:atom'] = 'http://www.w3.org/2005/Atom'
158         attrs['xmlns:media'] = 'http://search.yahoo.com/mrss/'
159         attrs['xmlns:dc'] = "http://purl.org/dc/elements/1.1/"
160         return attrs
161
162     def add_root_elements(self, handler):
163         super(OmnibusFeedType, self).add_root_elements(handler)
164         podcast_helper_add_root_elements(self, handler)
165
166     def add_item_elements(self, handler, item):
167         super(OmnibusFeedType, self).add_item_elements(handler, item)
168
169         # The below is a bit of a cheat, I assume anything in the ominbus
170         # feed that has an enclosure must be a podcast.  Probably not true
171         # as such in the general case, but enough for this case, I think.
172         if item['enclosure']:
173             podcast_helper_add_item_elements(self, handler, item)
174         else:
175             # Block things that don't have an enclosure from iTunes in
176             # case someone uploads this feed there.
177             handler.addQuickElement("itunes:block", 'Yes')
178
179
180
181 # http://www.feedforall.com/itune-tutorial-tags.htm
182 # http://www.feedforall.com/mediarss.htm
183 class iTunesFeedType(Rss201rev2Feed):
184     def root_attributes(self):
185         attrs = super(iTunesFeedType, self).root_attributes()
186         attrs['xmlns:itunes'] = 'http://www.itunes.com/dtds/podcast-1.0.dtd'
187         attrs['xmlns:atom'] = 'http://www.w3.org/2005/Atom'
188         attrs['xmlns:media'] = 'http://search.yahoo.com/mrss/'
189         attrs['xmlns:dc'] = "http://purl.org/dc/elements/1.1/"
190         return attrs
191
192     def add_root_elements(self, handler):
193         super(iTunesFeedType, self).add_root_elements(handler)
194         podcast_helper_add_root_elements(self, handler)
195
196     def add_item_elements(self, handler, item):
197         super(iTunesFeedType, self).add_item_elements(handler, item)
198         podcast_helper_add_item_elements(self, handler, item)
199
200
201 class CastFeed(CastFeedBase):
202     feed_type = iTunesFeedType
203     title = "Free as in Freedom"
204     link = "/cast/"
205     description = "A bi-weekly discussion of legal, policy, and other issues in the FLOSS world, including interviews from Brooklyn, New York, USA.  Presented by Karen Sandler and Bradley M. Kuhn."
206     author_email = "podcast@faif.us"
207     author_link = "http://www.faif.us/"
208     author_name = "Free as in Freedom"
209     title_template = "feeds/podcast_title.html"
210     description_template = "feeds/podcast_description.html"
211     def items(self):
212         return Cast.objects.filter(pub_date__lte=datetime.now()).order_by('-pub_date')
213     def item_pubdate(self, item):
214         return item.pub_date
215
216     def item_link(self, item):
217         return item.get_absolute_url()
218
219     def item_author_email(self, obj):
220         return "oggcast@faif.us"
221
222     def item_author_name(self, obj):
223         return "Free as in Freedom"
224
225     def item_author_link(self, obj):
226         return "http://faif.us"
227
228     def item_categories(self, item):
229         return  ('Technology',)
230
231     def copyright_holder(self): return "Free as in Freedom"
232
233     def license_no_html(self): return "Licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License."
234
235     def feed_extra_kwargs(self, obj):
236         return for_podcast_feed_extra_kwargs(self, obj)
237
238     def item_extra_kwargs(self, item):
239         return for_podcast_item_extra_kwargs(self, item)
240
241 # FIXME: 
242 # GUEST NAME GOES HERE!!!
243 #<itunes:author>
244 #     If applicable, at the item level, this tag can contain information
245 #     about the person(s) featured on a specific episode.
246
247
248 class Mp3CastFeed(CastFeed):
249     def item_enclosure_mime_type(self): return "audio/mpeg"
250     def item_enclosure_url(self, item):
251         return "http://faif.us" + item.mp3_path
252     def item_enclosure_length(self, item):
253         return item.mp3_length
254
255 class OggCastFeed(CastFeed):
256     def item_enclosure_mime_type(self): return "audio/ogg"
257     def item_enclosure_url(self, item):
258         return "http://faif.us" + item.ogg_path
259     def item_enclosure_length(self, item):
260         return item.ogg_length
261
262 feed_dict = {
263     'cast-ogg': OggCastFeed,
264     'cast-mp3': Mp3CastFeed,
265 }
266
267 # make each feed know its canonical url
268 for k, v in feed_dict.items():
269     v.get_absolute_url = '/feeds/%s/' % k
270
271 def view(request):
272     """Listing of all available feeds
273     """
274
275     feeds = feed_dict.values()
276     return render_to_response("feeds.html", {'feeds': feeds})