diff --git a/www/conservancy/settings.py b/www/conservancy/settings.py index a478584c..40a70b21 100644 --- a/www/conservancy/settings.py +++ b/www/conservancy/settings.py @@ -97,6 +97,8 @@ INSTALLED_APPS = [ 'conservancy.apps.fundgoal', 'conservancy.apps.assignment', 'conservancy.apps.fossy', + 'podjango', # Here so that the templates are found + 'podjango.apps.cast', ] DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' diff --git a/www/conservancy/urls.py b/www/conservancy/urls.py index 92127b05..791fa473 100644 --- a/www/conservancy/urls.py +++ b/www/conservancy/urls.py @@ -67,4 +67,5 @@ urlpatterns = [ url(r'^assignment/', include('conservancy.apps.assignment.urls')), url(r'^fossy/$', static_views.index), url(r'^fossy/', include('conservancy.apps.fossy.urls')), + url(r'^faif/', include('podjango.urls')), ] diff --git a/www/podjango/__init__.py b/www/podjango/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/www/podjango/apps/__init__.py b/www/podjango/apps/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/www/podjango/apps/cast/__init__.py b/www/podjango/apps/cast/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/www/podjango/apps/cast/admin.py b/www/podjango/apps/cast/admin.py new file mode 100644 index 00000000..b9623202 --- /dev/null +++ b/www/podjango/apps/cast/admin.py @@ -0,0 +1,37 @@ +# Copyright (C) 2008 Bradley M. Kuhn +# 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 . +# +from django.contrib import admin + +from .models import CastTag, Cast + +class CastTagAdmin(admin.ModelAdmin): + prepopulated_fields = {'slug': ('label',)} + +admin.site.register(CastTag, CastTagAdmin) + +class CastAdmin(admin.ModelAdmin): + list_display = ('pub_date', 'title') + list_filter = ['pub_date'] + date_hierarchy = 'pub_date' + search_fields = ['title', 'summary', 'body'] + prepopulated_fields = {'slug': ("title",)} + filter_horizontal = ('tags',) + + +admin.site.register(Cast, CastAdmin) diff --git a/www/podjango/apps/cast/migrations/0001_initial.py b/www/podjango/apps/cast/migrations/0001_initial.py new file mode 100644 index 00000000..a9cb5a22 --- /dev/null +++ b/www/podjango/apps/cast/migrations/0001_initial.py @@ -0,0 +1,50 @@ +# Generated by Django 3.2.19 on 2023-09-21 08:55 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='CastTag', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('label', models.CharField(max_length=100)), + ('slug', models.SlugField()), + ], + options={ + 'db_table': 'cast_tags', + }, + ), + migrations.CreateModel( + name='Cast', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=200)), + ('slug', models.SlugField(unique=True)), + ('summary', models.TextField(help_text='Use raw HTML. This summary is not included at the beginning of the body when the entry is displayed. It used only for the material on the front page.')), + ('body', models.TextField(help_text='Use raw HTML. Include the full body of any show notes or other information about this episode. It will be labelled on the site as Show Notes. It is included on the detail entry, and in the description data on the cast RSS feed.')), + ('pub_date', models.DateTimeField()), + ('ogg_path', models.CharField(blank=True, help_text='Local filename of the Ogg file, relative to webroot. File should be uploaded into the static media area for casts.', max_length=300)), + ('mp3_path', models.CharField(blank=True, help_text='Local filename of the mp3 file, relative to webroot. File should be uploaded into the static media area for casts.', max_length=300)), + ('ogg_length', models.IntegerField(help_text='size in bytes of ogg file')), + ('mp3_length', models.IntegerField(help_text='size in bytes of mp3 file')), + ('duration', models.CharField(help_text='length in hh:mm:ss of mp3 file', max_length=8)), + ('date_created', models.DateTimeField(auto_now_add=True)), + ('date_last_modified', models.DateTimeField(auto_now=True)), + ('tags', models.ManyToManyField(blank=True, to='cast.CastTag')), + ], + options={ + 'verbose_name_plural': 'casts', + 'db_table': 'casts_entries', + 'ordering': ('-pub_date',), + 'get_latest_by': 'pub_date', + }, + ), + ] diff --git a/www/podjango/apps/cast/migrations/__init__.py b/www/podjango/apps/cast/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/www/podjango/apps/cast/models.py b/www/podjango/apps/cast/models.py new file mode 100644 index 00000000..151c453e --- /dev/null +++ b/www/podjango/apps/cast/models.py @@ -0,0 +1,76 @@ +# Copyright (C) 2008 Bradley M. Kuhn +# 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 . +# +from django.db import models +from django.conf import settings +#from podjango.apps.staff.models import Person +from datetime import datetime, timedelta + +class CastTag(models.Model): + """Tagging for casts""" + + label = models.CharField(max_length=100) + slug = models.SlugField() + + class Meta: + db_table = 'cast_tags' # legacy + + def __unicode__(self): + return self.label + + def get_absolute_url(self): + return "/cast/?tag=%s" % self.slug + +class Cast(models.Model): + """Cast""" + + title = models.CharField(max_length=200) + slug = models.SlugField(unique=True) + summary = models.TextField(help_text="Use raw HTML. This summary is not included at the beginning of the body when the entry is displayed. It used only for the material on the front page.") + body = models.TextField(help_text="Use raw HTML. Include the full body of any show notes or other information about this episode. It will be labelled on the site as Show Notes. It is included on the detail entry, and in the description data on the cast RSS feed.") + pub_date = models.DateTimeField() +# poster = models.ForeignKey(Person) + tags = models.ManyToManyField(CastTag, blank=True) + ogg_path = models.CharField(max_length=300, blank=True, + help_text="Local filename of the Ogg file, relative to webroot. File should be uploaded into the static media area for casts.") + mp3_path = models.CharField(max_length=300, blank=True, + help_text="Local filename of the mp3 file, relative to webroot. File should be uploaded into the static media area for casts.") + ogg_length = models.IntegerField(blank=False, help_text="size in bytes of ogg file") + mp3_length = models.IntegerField(blank=False, help_text="size in bytes of mp3 file") + duration = models.CharField(max_length=8, blank=False, help_text="length in hh:mm:ss of mp3 file") + date_created = models.DateTimeField(auto_now_add=True) + date_last_modified = models.DateTimeField(auto_now=True) + + class Meta: + db_table = 'casts_entries' # legacy + verbose_name_plural = 'casts' + ordering = ('-pub_date',) + get_latest_by = 'pub_date' + + def __unicode__(self): + return self.title + + def get_absolute_url(self): + return "/cast/%s/%s/" % (self.pub_date.strftime("%Y/%b/%d").lower(), + self.slug) +# FIXME +# return (u"/cast/%s/" % (self.slug)) + + def is_recent(self): + return self.pub_date > (datetime.now() - timedelta(days=14)) + # question: does datetime.now() do a syscall each time is it called? diff --git a/www/podjango/apps/cast/templatetags/__init__.py b/www/podjango/apps/cast/templatetags/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/www/podjango/apps/cast/templatetags/date_within.py b/www/podjango/apps/cast/templatetags/date_within.py new file mode 100644 index 00000000..5213759d --- /dev/null +++ b/www/podjango/apps/cast/templatetags/date_within.py @@ -0,0 +1,9 @@ +from django import template +from datetime import timedelta, datetime + +register = template.Library() + +@register.filter +def date_within_past_days(value, arg): + # question: does datetime.now() do a syscall each time is it called? + return value > (datetime.now() - timedelta(days=int(arg))) diff --git a/www/podjango/apps/cast/urls.py b/www/podjango/apps/cast/urls.py new file mode 100644 index 00000000..d89501ab --- /dev/null +++ b/www/podjango/apps/cast/urls.py @@ -0,0 +1,82 @@ +# Copyright (C) 2008 Bradley M. Kuhn +# 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 . +# +from datetime import datetime + +from django.conf.urls import url +from django.views.generic.dates import DateDetailView, DayArchiveView, MonthArchiveView, YearArchiveView + +from .models import Cast, CastTag +from .views import custom_index, query + +extra_context = {} + +info_dict = { + 'queryset': Cast.objects.all(), + 'date_field': 'pub_date', + 'extra_context': extra_context, +} + +urlpatterns = [ + url(r'^(?P\d{4})/(?P[a-z]{3})/(?P\w{1,2})/(?P[-\w]+)/$', DateDetailView.as_view(**info_dict), name='detail'), + url(r'^(?P\d{4})/(?P[a-z]{3})/(?P\w{1,2})/$', DayArchiveView.as_view(**info_dict), name='day-archive'), + url(r'^(?P\d{4})/(?P[a-z]{3})/$', MonthArchiveView.as_view(**info_dict), name='month-archive'), + url(r'^(?P\d{4})/$', YearArchiveView.as_view(**info_dict), name='year-archive'), +# FIXME HOW DO I MAKE THE SLUG WORK WITH NO DATES IN IT. +# (r'^(?P[-\w]+)/$', 'object_detail', dict(info_dict, slug_field='slug')), +] + +urlpatterns += [ + url(r'^$', custom_index, dict(info_dict, paginate_by=20), name='cast'), + url(r'^query/$', query, name='query'), +] + +# Code to display authors and tags on each blog page + + +def all_tags_by_use_amount(): + """Returns all tags with an added 'cnt' attribute (how many times used) + + Also sorts the tags so most-used tags appear first. + """ + + # tally use amount + retval = [] + current = None + for obj in CastTag.objects.filter(cast__pub_date__lte=datetime.now(), + cast__isnull=False).order_by('label'): + if current is not None and obj.id == current.id: + current.cnt += 1 + else: + if current is not None: + retval.append(current) + current = obj + current.cnt = 1 + if current is not None: + retval.append(current) + + # sort and return + retval.sort(key=lambda x: -x.cnt) + return retval + +# The functions are passed to the context uncalled so they will be +# called for each web request. If we want to only make these database +# queries a single time when a web server process begins, call both +# functions below (i.e. make both lines below end in '()') + +extra_context['all_tags'] = all_tags_by_use_amount diff --git a/www/podjango/apps/cast/views.py b/www/podjango/apps/cast/views.py new file mode 100644 index 00000000..27dd58eb --- /dev/null +++ b/www/podjango/apps/cast/views.py @@ -0,0 +1,110 @@ +# Copyright (C) 2008 Bradley M. Kuhn +# 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 . +# +from datetime import datetime + +from django.views.generic.list import ListView +from django.shortcuts import get_object_or_404, render + +from .models import Cast, CastTag + + +def OR_filter(field_name, objs): + from django.db.models import Q + return reduce(lambda x, y: x | y, + [Q(**{field_name: x.id}) for x in objs]) + +def last_name(person): + return person.formal_name.rpartition(' ')[2] + +def custom_index(request, queryset, *args, **kwargs): + """Cast list view that allows scrolling and also shows an index by + year. + """ + + kwargs = kwargs.copy() + kwargs['extra_context'] = kwargs.get('extra_context', {}).copy() + extra_context = kwargs['extra_context'] + + date_field = kwargs['date_field'] + del kwargs['date_field'] + + if not kwargs.get('allow_future', False): + queryset = queryset.filter(**{'%s__lte' % date_field: datetime.now()}) + + authors = [] + if 'author' in request.GET: + authors = [get_object_or_404(Person, username=author) + for author in request.GET.getlist('author')] + extra_context['authors'] = authors + queryset = queryset.filter(OR_filter('author', authors)) + + tags = [] + if 'tag' in request.GET: + tags = [get_object_or_404(CastTag, slug=tag) + for tag in request.GET.getlist('tag')] + extra_context['tags'] = tags + queryset = queryset.filter(OR_filter('tags', tags)) + + if authors or tags: + query_string = '&'.join(['author=%s' % a.username for a in authors] + + ['tag=%s' % t.slug for t in tags]) + extra_context['query_string'] = query_string + + else: + date_list = queryset.dates(date_field, 'year') + extra_context['date_list'] = date_list + + # TODO + return render(request, 'podjango/cast/cast_list.html', {'object_list': queryset}) + +def query(request): + """Page to query the cast based on and tags + """ + + if request.GET: + d = request.GET.copy() + if 'authors' in d.getlist('all'): + d.setlist('author', []) # remove author queries + if 'tags' in d.getlist('all'): + d.setlist('tag', []) # remove tag queries + d.setlist('all', []) # remove "all" from the query string + + base_url = '/cast/' + if 'rss' in d: + base_url = '/feeds/cast/' + d.setlist('rss', []) # remove it + + query_string = d.urlencode() + + return relative_redirect(request, '%s%s%s' % (base_url, '?' if query_string else '', query_string)) + + else: + tags = CastTag.objects.all().order_by('label') + return render(request, 'podjango/cast/query.html', {'tags': tags}) + +def relative_redirect(request, path): + from django import http + from django.conf import settings + + host = http.get_host(request) + if settings.FORCE_CANONICAL_HOSTNAME: + host = settings.FORCE_CANONICAL_HOSTNAME + + url = "%s://%s%s" % (request.is_secure() and 'https' or 'http', host, path) + return http.HttpResponseRedirect(url) diff --git a/www/podjango/feeds.py b/www/podjango/feeds.py new file mode 100644 index 00000000..9b6b8d35 --- /dev/null +++ b/www/podjango/feeds.py @@ -0,0 +1,277 @@ +# Copyright (C) 2008, 2010 Bradley M. Kuhn +# 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 . +# + +from django.contrib.syndication.views import Feed +from django.utils.feedgenerator import Rss201rev2Feed +#from podjango.apps.staff.models import Person +from podjango.apps.cast.models import Cast + +from django.shortcuts import render +from django.conf import settings +from datetime import datetime + +import itertools +import operator + +# 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' : 'http://faif.us/img/cast/faif_144x144.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' : 'http://faif.us/img/cast/faif_300x300.jpg', + 'title' : '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' : 'Free as in Freedom', + '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', 'http://www.faif.us/code') + + 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}) + +class OmnibusFeedType(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) + + # The below is a bit of a cheat, I assume anything in the ominbus + # feed that has an enclosure must be a podcast. Probably not true + # as such in the general case, but enough for this case, I think. + if item['enclosure']: + podcast_helper_add_item_elements(self, handler, item) + else: + # Block things that don't have an enclosure from iTunes in + # case someone uploads this feed there. + handler.addQuickElement("itunes:block", 'Yes') + + + +# 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 = "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 = "http://www.faif.us/" + author_name = "Free as in Freedom" + title_template = "feeds/podcast_title.html" + description_template = "feeds/podcast_description.html" + def items(self): + return Cast.objects.filter(pub_date__lte=datetime.now()).order_by('-pub_date') + def item_pubdate(self, item): + return item.pub_date + + def item_link(self, item): + return item.get_absolute_url() + + def item_author_email(self, obj): + return "oggcast@faif.us" + + def item_author_name(self, obj): + return "Free as in Freedom" + + def item_author_link(self, obj): + return "http://faif.us" + + def item_categories(self, item): + return ('Technology',) + + def copyright_holder(self): return "Free as in Freedom" + + 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!!! +# +# 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 "http://faif.us" + item.mp3_path + 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 "http://faif.us" + item.ogg_path + def item_enclosure_length(self, item): + return item.ogg_length + +feed_dict = { + 'cast-ogg': OggCastFeed, + 'cast-mp3': Mp3CastFeed, +} + +# make each feed know its canonical url +for k, v in feed_dict.items(): + v.get_absolute_url = '/feeds/%s/' % k + +def view(request): + """Listing of all available feeds + """ + + feeds = feed_dict.values() + return render(request, "feeds.html", {'feeds': feeds}) diff --git a/www/podjango/frontpage.py b/www/podjango/frontpage.py new file mode 100644 index 00000000..5b3e6d58 --- /dev/null +++ b/www/podjango/frontpage.py @@ -0,0 +1,34 @@ +# Copyright 2010 Bradley M. Kuhn +# Copyright 2005-2008 James Garrison + +# This software's license gives you freedom; you can copy, convey, +# propagate, redistribute, modify and/or redistribute modified versions of +# 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 . + +from django.shortcuts import render +from podjango.apps.cast.models import Cast +from datetime import datetime, timedelta + +def view(request): + """Cast front page view + Performs all object queries necessary to render the front page. + """ + + cast = Cast.objects.all().filter(pub_date__lte=datetime.now())[:3] + + c = { + 'cast': cast, + } + return render(request, "podjango/frontpage.html", c) diff --git a/www/podjango/static/img/cast/audio_mp3_button.png b/www/podjango/static/img/cast/audio_mp3_button.png new file mode 100644 index 00000000..f3620987 Binary files /dev/null and b/www/podjango/static/img/cast/audio_mp3_button.png differ diff --git a/www/podjango/static/img/cast/audio_ogg_button.png b/www/podjango/static/img/cast/audio_ogg_button.png new file mode 100644 index 00000000..d0f81f7c Binary files /dev/null and b/www/podjango/static/img/cast/audio_ogg_button.png differ diff --git a/www/podjango/static/img/cast/faif_144x144.jpg b/www/podjango/static/img/cast/faif_144x144.jpg new file mode 100644 index 00000000..b2a571a3 Binary files /dev/null and b/www/podjango/static/img/cast/faif_144x144.jpg differ diff --git a/www/podjango/static/img/cast/faif_200x200.jpg b/www/podjango/static/img/cast/faif_200x200.jpg new file mode 100644 index 00000000..16538960 Binary files /dev/null and b/www/podjango/static/img/cast/faif_200x200.jpg differ diff --git a/www/podjango/static/img/cast/faif_300x300.jpg b/www/podjango/static/img/cast/faif_300x300.jpg new file mode 100644 index 00000000..df51528e Binary files /dev/null and b/www/podjango/static/img/cast/faif_300x300.jpg differ diff --git a/www/podjango/static/img/cast/faif_300x300.xcf b/www/podjango/static/img/cast/faif_300x300.xcf new file mode 100644 index 00000000..be55a690 Binary files /dev/null and b/www/podjango/static/img/cast/faif_300x300.xcf differ diff --git a/www/podjango/static/img/cast/halfbakedmedia-logo.png b/www/podjango/static/img/cast/halfbakedmedia-logo.png new file mode 100644 index 00000000..9956f784 Binary files /dev/null and b/www/podjango/static/img/cast/halfbakedmedia-logo.png differ diff --git a/www/podjango/static/img/cast/halfbakedmedia-logo_100px.png b/www/podjango/static/img/cast/halfbakedmedia-logo_100px.png new file mode 100644 index 00000000..6a51dc0e Binary files /dev/null and b/www/podjango/static/img/cast/halfbakedmedia-logo_100px.png differ diff --git a/www/podjango/static/img/cast/rss-audiomp3.png b/www/podjango/static/img/cast/rss-audiomp3.png new file mode 100644 index 00000000..eb768049 Binary files /dev/null and b/www/podjango/static/img/cast/rss-audiomp3.png differ diff --git a/www/podjango/static/img/cast/rss-audioogg.png b/www/podjango/static/img/cast/rss-audioogg.png new file mode 100644 index 00000000..00584942 Binary files /dev/null and b/www/podjango/static/img/cast/rss-audioogg.png differ diff --git a/www/podjango/static/img/cast/rss-miro.png b/www/podjango/static/img/cast/rss-miro.png new file mode 100644 index 00000000..53505248 Binary files /dev/null and b/www/podjango/static/img/cast/rss-miro.png differ diff --git a/www/podjango/static/img/feed-icon-14x14.png b/www/podjango/static/img/feed-icon-14x14.png new file mode 100644 index 00000000..b3c949d2 Binary files /dev/null and b/www/podjango/static/img/feed-icon-14x14.png differ diff --git a/www/podjango/static/license/index.html b/www/podjango/static/license/index.html new file mode 100644 index 00000000..9a646fc7 --- /dev/null +++ b/www/podjango/static/license/index.html @@ -0,0 +1,50 @@ +{% extends "base_standard.html" %} + +{% block content %} +

License and Source For This Website

+ +

The software running this website is copyrighted © 2007-2008, + Software Freedom Law Center, Inc. and copyrighted © 2010, 2016 Bradley M. + Kuhn. The software licensed under the terms of + the AGPLv3-or-later. + You can find a copy the sources of the Debian packages on + this site, but it may be more interesting to know that the site is + running on stock Debian lenny with Apache, Django, mod_python, and + sqlite installed, and that the main sources for the site's code itself + is hosted + on Github (temporarily, hopefully).

+ +

The documents on this website are copyrighted © 2010, 2012, 2015, 2016 Bradley + M. Kuhn and Karen M. Sandler, and are licensed + as CC-By-SA-4.0 + International. In some cases, the documents are dual-licensed + (AGPLv3-or-later|CC-By-SA-4.0-International), to make it more + convenient to intermix the document with code.

+ +

Creative
+   Commons LicenseThe content of this + audcast, the accompanying show notes and music are copyrighted + © 2010, 2011, 2012, 2013, 2014, 2015, 2016 by Bradley M. Kuhn, + Karen M. Sandler, Dan Lynch, and Mike + Tarantino. Some speech recording and other materials are + copyrighted by the individual speakers.

+ +

Creative Commons License + The content + of this + audcast, and the accompanying show notes and music are licensed + under the Creative + Commons Attribution-Share-Alike 3.0 USA license (CC BY-SA 4.0). + +

The icons for the RSS audio feeds and direct audio links are borrowed from + the PodPress project + and are licensed under GPLv2.

+ +{% endblock %} diff --git a/www/podjango/static/podjango.css b/www/podjango/static/podjango.css new file mode 100644 index 00000000..c952d9f9 --- /dev/null +++ b/www/podjango/static/podjango.css @@ -0,0 +1,241 @@ +* { margin: 0; padding: 0; } +img { border: 0; } +body { margin: 0; padding: 0; } +.clear { clear: both; } +.hidden { display: none; } + +p, h1, h2, h3, h4, h5, h6, #mainContent ul, #mainContent ol { + margin-top: 1em; + margin-bottom: 1em; +} + +body { +/* font-family: "Lucida Grande",Verdana,Lucida,Helvetica,Arial,sans-serif; */ + font-size: 95%; +} + +a { text-decoration: none; color: #146624; } +a:hover { text-decoration: underline; color: #665f14; } + +h1 { margin-top: .75em; margin-bottom: .5em; } +h2 { margin-top: .75em; margin-bottom: .5em; } +h3 { margin-top: .6em; margin-bottom: .4em; } + +#mainContent h1 { border-bottom: 1px solid #00334b; } +#mainContent h2 { border-bottom: 1px solid #aaa; } + +#mainContent ul, #mainContent ol { padding-left: 1.5em; } + +#mainContent img { margin: 3px; } + +.internalNavigate { width: 19%; float: right; } +#mainContent .internalNavigate ul { list-style-type: none; padding-left: 0; } +.internalNavigate ul li { margin-top: .3em; margin-bottom: .3em; } + +.affiliate-link { float: right; } +#affiliate-podjango, #affiliate-mr { + padding: 2px 10px; + white-space: nowrap; + font-size: x-small; + text-align: right; +} +#affiliate-podjango { background: #d4ff9d; } +#affiliate-mr { background: #ddd; } + +/* Header */ + +#podjangoheader { + height: 3.5em; + background-color: #12d5bc; + border-bottom: 1px solid #808080; +} +#podjangoheader h1 { + font-size: 3em; + /* -indent: -5000px; */ + /* margin: 0; /\* hide in favor of image *\/ */ +} +/* #logobutton { */ +/* display: block; */ +/* position: absolute; left: 17px; top: 10px; height: 120px; width: 440px; */ +/* background: url(/img/podjango-header.png) left center no-repeat; */ +/* } */ + + +/* Navigation bar */ +#navbar-outer { background: #CDDBDC; } +#navbar { margin-left: 104px; margin-top: 3px; float: left; display: inline; } +#navbar ul { list-style: none; } +#navbar-clear { clear: both; border-bottom: 1px solid #808080; } +#navbar li a { + display: block; + background: #fafafe; + padding: .2em .8em; + margin-right: 3px; + border: 1px solid #808080; + font-size: .8em; + background: #fff url(/img/nav-bg.png) bottom repeat-x; +} +#navbar li:hover a { background: #fff99d; } +#navbar li { float: left; display: inline; margin-bottom: 3px; } + +#mainContent { + margin-left: 50px; + margin-right: 50px; +} + +#container { + width: 100%; + overflow: hidden; +} +#container #mainContent { + background: #fffff; + margin-left: 210px; + margin-right: 50px; + padding-bottom: 32767px; + margin-bottom: -32767px; +} +#container #sidebar { + width: 200px; + float: left; + padding-bottom: 32767px; + margin-bottom: -32767px; + background-color: #e5e5e5; +} + +#container #sidebar li { + text-align: center; + list-style: none; + padding: 3px 10px 3px 10px; + margin: 5px; + border: 1px solid #CCC; + background: #fff url(/img/nav-bg.png) bottom repeat-x; +} + +#container #sidebar.Directors ul li.Directors, +#container #sidebar.Contact ul li.Contact, +#container #sidebar.Officers ul li.Officers, +#container #sidebar.Staff ul li.Staff, +#container #sidebar.Current ul li.Current, +#container #sidebar.Services ul li.Services, +#container #sidebar.Applying ul li.Applying +{ + color: #000033; + font-weight: bold; + background: #fff url(/img/nav-bg-up.png) top repeat-x; +} +#container #sidebar h2 { + text-align: center; + font-size: 150%; + margin: 1.5em 0 0.8em 0; +} +#container #sidebar hr { + width: 75%; + float: center; + clear: all; +} + +/* Navbar Submenus (currently unused) */ +#navbar li ul { display: none; border: 1px solid #444; } +#navbar li:hover ul { display: block; position: absolute; } +#navbar li ul li { float: none; } +#navbar li ul li a { border: 0px; margin: 0px; } + +.shaded { background: #ffeab7; padding: .1em .5em; margin-bottom: .5em; } + +.columns { + width: 100%; + overflow: hidden; +} + +.column-small { + width: 31%; + padding-bottom: 32767px; + margin-bottom: -32767px; + } +.column-large { + margin-left: 35%; + margin-right: 50px; + padding-bottom: 32767px; + margin-bottom: -32767px; +} + +.column-left { float: left; } +.column-right { float: right; } + +.column h2 { font-size: 1.25em; } +.column h3 { font-size: 1.1em; } + +.column h2 a { text-decoration: none; color: #000000; } +.column h2 a:hover { text-decoration: underline; } + +#podjangofooter { + margin-top: 1em; + border-top: 1px solid #ccc; + text-align: center; + clear: both; +} + +span.continued { + display: block; + font-size: .83em; + font-weight: bold; + margin-top: 1em; + margin-bottom: 1em; +} + +p.date { + font-style: italic; + font-size: .83em; + margin-bottom: .3em; + margin-top: .3em; +} + +a.feedlink /* RSS icon */ { display: block; float: right; font-size: 10pt; } + +blockquote, div.quote /* div.quote is used by podjango whitepaper */ { + margin-left: 2em; + margin-right: 2em; + padding-left: 1em; + padding-right: 1em; + border: 1px solid #fff; + background: #eee; +} + +.newsgraphic { float: right; } +.newsgraphic img { border: 1px solid #000; } + +.secondary_info { font-size: 83%; } +.next_page_button { float: right; } +.document_format { border: 1px solid #888; padding: .2em; background: #fff99d;} +.copyright_info { font-size: 90%; } +hr.footnote-separator { width: 80%; margin-left: auto; margin-right: auto; } + +/* Resources pages */ +div.download-formats { margin-top: 2em; margin-bottom: 2em; } +.download-formats p { display: inline; } +#mainContent .download-formats ul { display: inline; list-style: none; + padding-left: 0; } +.download-formats ul li { display: inline; padding-left: 2em; } + +/* Resources - book styles */ +hr.chapter-separator { display: none; } +h2.likechapterHead { text-align: center; } +h2.chapterHead { text-align: center; } +#mainContent ul.author { list-style-type: none; padding-left: 0; } +#mainContent div.footnotes { font-style: normal; } /* remove italics */ +span.sectionToc { padding-left: 2em; } /* indent TOC properly */ +span.subsectionToc { padding-left: 4em; } +span.subsubsectionToc { padding-left: 6em;} +.js, .js p, .js p.bibitem, .js p.bibitem-p { background-color: #cde7e9; } + +body.podjango-overview #navbar ul li.overview a, +body.podjango-Members #navbar ul li.Members a, +body.podjango-news #navbar ul li.news a, +body.podjango-blog #navbar ul li.blog a, +body.podjango-About #navbar ul li.About a, +body.podjango-donate #navbar ul li.donate a /* NO COMMA HERE! */ +{ + color: #000033; + font-weight: bold; + background: #fff url(/img/nav-bg-up.png) top repeat-x; +} \ No newline at end of file diff --git a/www/podjango/templates/podjango/base_podcast.html b/www/podjango/templates/podjango/base_podcast.html new file mode 100644 index 00000000..a9d411d2 --- /dev/null +++ b/www/podjango/templates/podjango/base_podcast.html @@ -0,0 +1,26 @@ + + + + + +{% extends "base_standard.html" %} + +{% block head %} + + +{% endblock %} + +{% block internal_navigate %} + +

Tags

+ + +

All oggcasts…

+ + + +{% endblock %} diff --git a/www/podjango/templates/podjango/base_standard.html b/www/podjango/templates/podjango/base_standard.html new file mode 100644 index 00000000..ab00392b --- /dev/null +++ b/www/podjango/templates/podjango/base_standard.html @@ -0,0 +1,48 @@ + + + + + + {% block title %}{% block subtitle %}{% endblock %}Free as in Freedom{% endblock %} + + + + + + {% block head %}{% endblock %} + + + + + {% block outercontent %}
+

We raised $2,515.72 toward Dan Lynch's trip to a conference to represent + the show and record content! We'll be coordinated with Dan about what + conference he wants to attend.

+ +

If you'd like to further support Free as in Freedom, + please become a supporter of + Software + Freedom Conservancy, the charity where Bradley and Karen work.

+ + {% block content %}{% endblock %}
{% endblock %} +
+ {% block copyright_info %} + +

Free as in Freedom is produced by Dan Lynch + of danlynch.org. Theme + music written and performed + by Mike Tarantino + with Charlie Paxson on drums.

+ +

Main Page | License of show + and website | Ogg Feed | MP3 Feed

+ +{% endblock %} +
+ + diff --git a/www/podjango/templates/podjango/cast/cast_archive_day.html b/www/podjango/templates/podjango/cast/cast_archive_day.html new file mode 100644 index 00000000..39676e0d --- /dev/null +++ b/www/podjango/templates/podjango/cast/cast_archive_day.html @@ -0,0 +1,34 @@ + + + + +{% extends "podjango/base_podcast.html" %} + +{% block subtitle %}Free as in Freedom Archive: {{ day|date:"F j, Y" }} - {% endblock %} + +{% block content %} + + +

[Ogg/Vorbis Audio RSS] +[MP3 Audio RSS] +Free as in Freedom

+ +

Free as in Freedom Archive: {{ day|date:"F j, Y" }}

+ +{% for object in object_list %} +
+

{{ object.pub_date|date:"F j, Y" }}

+

{{ object.title|safe }}

+ {{ object.summary|safe }} +

Read More...

+

Released on {{ object.pub_date|date:"F j, Y" }}; its running time is {{ object.duration }}

+ {% if object.tags.all %}

Tags: {% for tag in object.tags.all %}{{ tag.label }}{% if not forloop.last %}, {% endif %}{% endfor %}

{% endif %} +
+{% endfor %} + +{% endblock %} diff --git a/www/podjango/templates/podjango/cast/cast_archive_month.html b/www/podjango/templates/podjango/cast/cast_archive_month.html new file mode 100644 index 00000000..dd49e03f --- /dev/null +++ b/www/podjango/templates/podjango/cast/cast_archive_month.html @@ -0,0 +1,34 @@ + + + + +{% extends "podjango/base_podcast.html" %} + +{% block subtitle %}Free as in Freedom Archive: {{ month|date:"F, Y" }} - {% endblock %} + +{% block content %} + + +

[Ogg/Vorbis Audio RSS] +[MP3 Audio RSS] +Free as in Freedom

+ +

Free as in Freedom Archive: {{ month|date:"F, Y" }}

+ +{% for object in object_list %} +
+

{{ object.pub_date|date:"F j, Y" }}

+

{{ object.title|safe }}

+ {{ object.summary|safe }} +

Read More...

+

Released on {{ object.pub_date|date:"F j, Y" }}; its running time is {{ object.duration }}

+ {% if object.tags.all %}

Tags: {% for tag in object.tags.all %}{{ tag.label }}{% if not forloop.last %}, {% endif %}{% endfor %}

{% endif %} +
+{% endfor %} + +{% endblock %} diff --git a/www/podjango/templates/podjango/cast/cast_archive_year.html b/www/podjango/templates/podjango/cast/cast_archive_year.html new file mode 100644 index 00000000..3e59fb3b --- /dev/null +++ b/www/podjango/templates/podjango/cast/cast_archive_year.html @@ -0,0 +1,28 @@ + + + + +{% extends "podjango/base_podcast.html" %} + +{% block subtitle %}Free as in Freedom Archive: {{ year }} - {% endblock %} + +{% block content %} + +

[Ogg/Vorbis Audio RSS] +[MP3 Audio RSS] +Free as in Freedom

+ +

Free as in Freedom Archive: {{ year }}

+ +
    +{% for object in object_list %} +
  • {{ object.title|safe }}
    + {{ object.pub_date|date:"F j, Y" }}; duration: {{ object.duration }}
  • +{% endfor %} +
+ +{% endblock %} diff --git a/www/podjango/templates/podjango/cast/cast_detail.html b/www/podjango/templates/podjango/cast/cast_detail.html new file mode 100644 index 00000000..7877827f --- /dev/null +++ b/www/podjango/templates/podjango/cast/cast_detail.html @@ -0,0 +1,57 @@ + + + + +{% extends "podjango/base_podcast.html" %} + +{% block subtitle %}{{ object.title|striptags }} - Free as in Freedom - {% endblock %} + +{% block content %} + +

[Ogg/Vorbis Audio RSS] +[MP3 Audio RSS] +Free as in Freedom

+ +

[Get cast in Ogg/Vorbis
+                                          format] +[Get cast in MP3 format] + + +{{ object.title|safe }}

+ +

{{ object.pub_date|date:"j F Y" }}

+ +

Download

+ +oggmp3 + +

Summary

+ +{{ object.summary|safe }} + +

+This show was released on {{ object.pub_date|date:"l j F Y" }}; its +running time is {{ object.duration}}.

+ +

Show Notes

+{{ object.body|safe }} +
+ +
+ +

Send feedback and comments on the oggcast +to <oggcast@faif.us>. +You can keep in touch with Free as in Freedom +on our IRC channel, #faif on irc.freenode.net, and +by following @faif on identi.ca.

+ +{% if object.tags.all %}

Tags: {% for tag in object.tags.all %}{{ tag.label }}{% if not forloop.last %}, {% endif %}{% endfor %}

{% endif %} + +

Other FaiF oggcasts…

+ +{% endblock %} diff --git a/www/podjango/templates/podjango/cast/cast_list.html b/www/podjango/templates/podjango/cast/cast_list.html new file mode 100644 index 00000000..2b6c4683 --- /dev/null +++ b/www/podjango/templates/podjango/cast/cast_list.html @@ -0,0 +1,62 @@ + + + + +{% extends "podjango/base_podcast.html" %} + +{% block subtitle %}Free as in Freedom - {% endblock %} + +{% block content %} + +

[Ogg/Vorbis Audio RSS] +[MP3 Audio RSS] +Free as in Freedom

+ +{% if tags %} +

Displaying casts +tagged {% for tag in tags %}{% if not forloop.last %}{% if not forloop.first %}, {% endif %}{% endif %}{{ tag.label }}{% ifequal forloop.revcounter 2 %} or {% endifequal %}{% endfor %} +

+{% endif %} + +{% for object in object_list %} +
+

{{ object.pub_date|date:"F j, Y" }}

+

+[Get cast in Ogg/Vorbis
+                                          format] +[Get cast in MP3 format] + +{{ object.title|safe }}

+

Summary

+ + {{ object.summary|safe }} + +

+ This show was released on {{ object.pub_date|date:"l j F Y" }}; its + running time is {{ object.duration}}.

+

Show Notes

+ {{ object.body|safe }} +
+ {% if object.tags.all %}

Tags: {% for tag in object.tags.all %}{{ tag.label }}{% if not forloop.last %}, {% endif %}{% endfor %}

{% endif %} +
+{% endfor %} + +

+{% if has_next %}Next page (older) »{% endif %} +{% if has_previous %}« Previous page (newer){% endif %} +

+
+ +{% if date_list %} +

Index by date

+ +{% endif %} + +{% endblock %} diff --git a/www/podjango/templates/podjango/feeds.html b/www/podjango/templates/podjango/feeds.html new file mode 100644 index 00000000..bf568707 --- /dev/null +++ b/www/podjango/templates/podjango/feeds.html @@ -0,0 +1,18 @@ +{% extends "base_standard.html" %} + +{% block subtitle %}News Feeds - {% endblock %} + +{% block content %} + +

Feeds Available at faif.us

+ +

All feeds are RSS 2.0.

+ +
    +{% for feed in feeds %} +
  • {{ feed.title }} — +{{ feed.description|safe }}
  • +{% endfor %} +
+ +{% endblock %} diff --git a/www/podjango/templates/podjango/feeds/omnibus_description.html b/www/podjango/templates/podjango/feeds/omnibus_description.html new file mode 100644 index 00000000..2edf925f --- /dev/null +++ b/www/podjango/templates/podjango/feeds/omnibus_description.html @@ -0,0 +1,16 @@ + + + + + +{% ifequal obj.omnibus_type "news" %} +

A news item from SFLC.

+{% endifequal %} +{% ifequal obj.omnibus_type "podcast" %} +

An episode of the Software Freedom Law Show.

+{% endifequal %} +{% ifequal obj.omnibus_type "event" %} +

An upcoming event related to the SFLC .

+{% endifequal %} +{% include obj.omnibus_feed_description_template %} diff --git a/www/podjango/templates/podjango/feeds/omnibus_title.html b/www/podjango/templates/podjango/feeds/omnibus_title.html new file mode 100644 index 00000000..66cec083 --- /dev/null +++ b/www/podjango/templates/podjango/feeds/omnibus_title.html @@ -0,0 +1,5 @@ + + + +{% include obj.omnibus_feed_title_template %} diff --git a/www/podjango/templates/podjango/feeds/podcast_description.html b/www/podjango/templates/podjango/feeds/podcast_description.html new file mode 100644 index 00000000..60162ca8 --- /dev/null +++ b/www/podjango/templates/podjango/feeds/podcast_description.html @@ -0,0 +1,38 @@ +

+[Direct download of cast in Ogg/Vorbis
+                                          format] +[Direct download of cast in MP3 format] +

+

+{{ obj.summary|safe }} +

+

Show Notes:

+ +{{ obj.body|safe }} + +
+ +

Send feedback and comments on the cast +to <oggcast@faif.us>. +You can keep in touch with Free as in Freedom on our IRC channel, #faif on irc.freenode.net, and + by following Conservancy on + identi.ca and and Twitter.

+ +

Free as in Freedom is produced by Dan Lynch + of danlynch.org. +Theme + music written and performed + by Mike Tarantino + with Charlie Paxson on drums.

+ +

Creative Commons License + The content + of this + audcast, and the accompanying show notes and music are licensed + under the Creative + Commons Attribution-Share-Alike 4.0 license (CC BY-SA 4.0). +

diff --git a/www/podjango/templates/podjango/feeds/podcast_title.html b/www/podjango/templates/podjango/feeds/podcast_title.html new file mode 100644 index 00000000..8d1d5bd5 --- /dev/null +++ b/www/podjango/templates/podjango/feeds/podcast_title.html @@ -0,0 +1 @@ +{{ obj.title|striptags|safe }} diff --git a/www/podjango/templates/podjango/frontpage.html b/www/podjango/templates/podjango/frontpage.html new file mode 100644 index 00000000..e8cdc3c2 --- /dev/null +++ b/www/podjango/templates/podjango/frontpage.html @@ -0,0 +1,59 @@ +{% extends "podjango/base_podcast.html" %} + +{% block content %} + +
+ + + +

Free as in Freedom

+ +

Free as in Freedom is a bi-weekly oggcast, hosted and presented by +Bradley M. Kuhn and Karen Sandler. +The discussion includes legal, policy, and many other issues in the Free, Libre, +and Open Source Software (FLOSS) world. Occasionally, guests join +Bradley and Karen to discuss various topics regarding FLOSS.

+ +

You can email feedback on the show + to <oggcast@faif.us>, or join + bkuhn and other listeners in our IRC channel, #faif on + irc.freenode.net.

+ + +

Follow FaiF's RSS, and Other Feeds

+ +

There is RSS for both ogg format + and mp3 format. These links might + work if you want to subscribe to the show with proprietary Apple devices.

+ +

If you're interested you can + follow Karen, + and the podcast itself + on Twitter. + Also, Bradley + and Karen both have blogs you can + read.

+ +

+[Ogg/Vorbis Audio RSS] +[MP3 Audio
+       RSS]Recent Free as in Freedom Shows

+ +{% for cc in cast %} +
+[Get cast in Ogg/Vorbis
+                                          format] +[Get cast in MP3 format] +

{{ cc.pub_date|date:"F j, Y" }}

+

{{ cc.title|safe }}

+{{ cc.summary|safe }} +
+{% endfor %} +

All oggcasts…

+
+{% endblock %} diff --git a/www/podjango/urls.py b/www/podjango/urls.py new file mode 100644 index 00000000..9372a9b9 --- /dev/null +++ b/www/podjango/urls.py @@ -0,0 +1,43 @@ +# Copyright 2010 Bradley M. Kuhn +# Copyright 2005-2008 James Garrison + +# This software's license gives you freedom; you can copy, convey, +# propagate, redistribute, modify and/or redistribute modified versions of +# 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 . + +from django.conf import settings +from django.conf.urls import url, include +from django.contrib import admin +from django.contrib.syndication.views import Feed + +from podjango.feeds import feed_dict, view, OggCastFeed +from podjango import frontpage + +#handler404 = 'modpythoncustom.view404' + +admin.autodiscover() + +app_name = 'podjango' +urlpatterns = [ + url(r'^$', frontpage.view), + url(r'^cast/?', include('podjango.apps.cast.urls')), + url(r'^feeds/cast-ogg/?$', OggCastFeed(), name='feed-ogg'), + url(r'^feeds/cast-mp3/?$', OggCastFeed(), name='feed-mp3'), + url(r'^feeds/$', view, name='feeds'), +] + +if settings.DEBUG: + from django.conf.urls.static import static + urlpatterns += static('/', document_root='podjango/static')