Changeset - 956f8c6fdaad
[Not reviewed]
3 14 6
Ben Sturmfels (bsturmfels) - 1 month ago 2024-04-25 05:32:24
ben@sturm.com.au
podjango: Add "Podcast" model to support multiple podcasts

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.
23 files changed with 277 insertions and 202 deletions:
0 comments (0 inline, 0 general)
conservancy/podjango/admin.py
Show inline comments
...
 
@@ -15,26 +15,29 @@
 
# 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 django.contrib import admin
 

	
 
from .models import Cast, CastTag
 
from .models import Cast, CastTag, Podcast
 

	
 

	
 
@admin.register(Podcast)
 
class PodcastAdmin(admin.ModelAdmin):
 
    prepopulated_fields = {'slug': ('title',)}
 

	
 

	
 
@admin.register(CastTag)
 
class CastTagAdmin(admin.ModelAdmin):
 
    prepopulated_fields = {'slug': ('label',)}
 

	
 

	
 

	
 

	
 
@admin.register(Cast)
 
class CastAdmin(admin.ModelAdmin):
 
    list_display = ('pub_date', 'title')
 
    list_filter = ['pub_date']
 
    list_filter = ['pub_date', 'podcast']
 
    date_hierarchy = 'pub_date'
 
    search_fields = ['title', 'summary', 'body']
 
    prepopulated_fields = {'slug': ("title",)}
 
    filter_horizontal = ('tags',)
 

	
 

	
conservancy/podjango/feeds.py
Show inline comments
...
 
@@ -18,16 +18,18 @@
 
#
 

	
 
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 render
 
from django.shortcuts import get_object_or_404, render
 
from django.urls import reverse
 
from django.utils.feedgenerator import Rss201rev2Feed
 

	
 
from .models import Cast
 
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):
...
 
@@ -100,13 +102,13 @@ def podcast_helper_add_root_elements(self, handler):
 
    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('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']})
 

	
...
 
@@ -185,13 +187,13 @@ class iTunesFeedType(Rss201rev2Feed):
 
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 = "http://www.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
...
 
@@ -199,29 +201,33 @@ class CastFeed(CastFeedBase):
 
        # 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 items(self):
 
        return Cast.objects.filter(pub_date__lte=datetime.now()).order_by('-pub_date')
 
    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 item.get_absolute_url()
 
        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 "http://faif.us"
 
        return "https://sfconservancy.org"
 

	
 
    def item_categories(self, item):
 
        return ('Technology',)
 

	
 
    def copyright_holder(self):
 
        return "Software Freedom Conservancy"
...
 
@@ -261,22 +267,20 @@ class OggCastFeed(CastFeed):
 
        return add_domain(self.current_site.domain, item.ogg_path, self.is_secure)
 

	
 
    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):
 
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", {'feeds': feeds})
 
    return render(request, "feeds.html", {'podcast': podcast,
 
                                          'feeds': feeds})
conservancy/podjango/frontpage.py
Show inline comments
...
 
@@ -16,22 +16,22 @@
 
# 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.shortcuts import render
 
from django.shortcuts import get_object_or_404, render
 

	
 
from .models import Cast
 
from .models import Cast, Podcast
 

	
 

	
 
def view(request):
 
def view(request, podcast_slug):
 
    """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]
 

	
 
    podcast = get_object_or_404(Podcast, slug=podcast_slug)
 
    cast = Cast.objects.filter(podcast=podcast, pub_date__lte=datetime.now())[:3]
 
    c = {
 
        'cast': cast,
 
        'podcast': podcast,
 
    }
 
    return render(request, "podjango/frontpage.html", c)
conservancy/podjango/migrations/0003_podcast_cast_podcast.py
Show inline comments
 
new file 100644
 
# Generated by Django 4.2.11 on 2024-04-24 04:03
 

	
 
from django.db import migrations, models
 

	
 

	
 
class Migration(migrations.Migration):
 

	
 
    dependencies = [
 
        ('podjango', '0002_alter_cast_tags'),
 
    ]
 

	
 
    operations = [
 
        migrations.CreateModel(
 
            name='Podcast',
 
            fields=[
 
                (
 
                    'id',
 
                    models.AutoField(
 
                        auto_created=True,
 
                        primary_key=True,
 
                        serialize=False,
 
                        verbose_name='ID',
 
                    ),
 
                ),
 
                ('title', models.CharField(max_length=50)),
 
            ],
 
        ),
 
        migrations.AddField(
 
            model_name='cast',
 
            name='podcast',
 
            field=models.ManyToManyField(to='podjango.podcast'),
 
        ),
 
    ]
conservancy/podjango/migrations/0004_alter_cast_options_alter_casttag_options_and_more.py
Show inline comments
 
new file 100644
 
# Generated by Django 4.2.11 on 2024-04-24 04:10
 

	
 
from django.db import migrations, models
 

	
 

	
 
class Migration(migrations.Migration):
 

	
 
    dependencies = [
 
        ('podjango', '0003_podcast_cast_podcast'),
 
    ]
 

	
 
    operations = [
 
        migrations.AlterModelOptions(
 
            name='cast',
 
            options={
 
                'get_latest_by': 'pub_date',
 
                'ordering': ('-pub_date',),
 
                'verbose_name': 'episode',
 
                'verbose_name_plural': 'episodes',
 
            },
 
        ),
 
        migrations.AlterModelOptions(
 
            name='casttag',
 
            options={
 
                'verbose_name': 'episode tag',
 
                'verbose_name_plural': 'episode tags',
 
            },
 
        ),
 
        migrations.AddField(
 
            model_name='podcast',
 
            name='slug',
 
            field=models.SlugField(default='', unique=True),
 
            preserve_default=False,
 
        ),
 
    ]
conservancy/podjango/migrations/0005_podcast_long_description.py
Show inline comments
 
new file 100644
 
# Generated by Django 4.2.11 on 2024-04-25 01:21
 

	
 
from django.db import migrations, models
 

	
 

	
 
class Migration(migrations.Migration):
 

	
 
    dependencies = [
 
        ('podjango', '0004_alter_cast_options_alter_casttag_options_and_more'),
 
    ]
 

	
 
    operations = [
 
        migrations.AddField(
 
            model_name='podcast',
 
            name='long_description',
 
            field=models.TextField(blank=True),
 
        ),
 
    ]
conservancy/podjango/models.py
Show inline comments
...
 
@@ -19,42 +19,53 @@
 
from datetime import datetime, timedelta
 

	
 
from django.db import models
 
from django.urls import reverse
 

	
 

	
 
class Podcast(models.Model):
 
    """An ongoing series of episodes."""
 
    title = models.CharField(max_length=50)
 
    slug = models.SlugField(unique=True)
 
    long_description = models.TextField(blank=True)
 

	
 
    def __str__(self):
 
        return self.title
 

	
 

	
 
class CastTag(models.Model):
 
    """Tagging for casts"""
 

	
 
    label = models.CharField(max_length=100)
 
    slug = models.SlugField()
 

	
 
    class Meta:
 
        db_table = 'cast_tags'  # legacy
 
        verbose_name = 'episode tag'
 
        verbose_name_plural = 'episode tags'
 

	
 

	
 
    def __str__(self):
 
        return self.label
 

	
 
    def get_absolute_url(self):
 
        return reverse('podjango:cast') + "?tag=%s" % self.slug
 

	
 

	
 
class CastManager(models.Manager):
 
    def get_queryset(self):
 
        # Temporarily filter out old FaiF episodes we've imported.
 
        return super().get_queryset().filter(pub_date__year__gte=2024)
 

	
 

	
 
class Cast(models.Model):
 
    """Cast"""
 
    """A podcast episode."""
 

	
 
    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()
 
    podcast = models.ManyToManyField(Podcast)
 
    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,
...
 
@@ -66,27 +77,20 @@ class Cast(models.Model):
 
    date_last_modified = models.DateTimeField(auto_now=True)
 

	
 
    objects = CastManager()
 

	
 
    class Meta:
 
        db_table = 'casts_entries'  # legacy
 
        verbose_name_plural = 'casts'
 
        verbose_name = 'episode'
 
        verbose_name_plural = 'episodes'
 
        ordering = ('-pub_date',)
 
        get_latest_by = 'pub_date'
 

	
 
    def __str__(self):
 
        return self.title
 

	
 
    def get_absolute_url(self):
 
        return reverse(
 
            'podjango:detail',
 
            kwargs={
 
                'year': self.pub_date.year,
 
                'month': self.pub_date.strftime("%b").lower(),
 
                'day': self.pub_date.day,
 
                'slug': self.slug,
 
            }
 
        )
 
    def month_str(self):
 
        return self.pub_date.strftime("%b").lower()
 

	
 
    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?
conservancy/podjango/templates/podjango/base_podcast.html
Show inline comments
...
 
@@ -14,22 +14,7 @@
 
 ul {
 
   padding-left: 2rem;
 
   margin-bottom: 1rem;
 
 }
 
 a.feedlink img { margin-left: 0.5rem }
 
</style>
 
<link rel="alternate" type="application/rss+xml" title="MP3 Audio Feed" href="{% url 'podjango:feed-mp3' %}" />
 
<link rel="alternate" type="application/rss+xml" title="Ogg/Vorbis Audio Feed" href="{% url 'podjango:feed-ogg' %}" />
 
{% endblock %}
 

	
 
{% comment %}
 
{% block internal_navigate %}
 
<h3>Tags</h3>
 
<ul>
 
{% for tag in all_tags %}
 
<li><a href="{{ tag.get_absolute_url }}">{{ tag.label }}</a></li>
 
{% endfor %}
 
</ul>
 

	
 
<p><a href="{% url 'podjango:cast' %}">All oggcasts&hellip;</a></p>
 
{% endblock %}
 
{% endcomment %}
conservancy/podjango/templates/podjango/cast/cast_archive_day.html
Show inline comments
 
deleted file
conservancy/podjango/templates/podjango/cast/cast_archive_month.html
Show inline comments
 
deleted file
conservancy/podjango/templates/podjango/cast/cast_archive_year.html
Show inline comments
 
deleted file
conservancy/podjango/templates/podjango/cast_detail.html
Show inline comments
 
<!-- FIXME: SFLC specific content -->
 
<!--  Copyright (C) 2008       Bradley M. Kuhn <bkuhn@ebb.org> -->
 
<!-- Permission is granted to copy, modify, redistribute, propagate, -->
 
<!-- and/or convey this template in any form. -->
 
{% extends "podjango/base_podcast.html" %}
 

	
 
{% block subtitle %}{{ object.title|striptags }} - The Corresponding Source  - {% endblock %}
 
{% load podjango %}
 

	
 
{% block subtitle %}{{ object.title|striptags }} - {{ podcast.title }} - {% endblock %}
 

	
 
{% block content %}
 

	
 
<h1>{% include 'podjango/feed_links.inc.html' %} <a href="{% url 'podjango:cast-home' %}">The Corresponding Source</h1>
 
<h1>{% include 'podjango/feed_links.inc.html' %} <a href="{% url 'podjango:cast-home' podcast_slug=podcast.slug %}">{{ podcast.title }}</h1>
 

	
 
<h2><a class="feedlink" href="{{ object.ogg_path }}">{% include 'podjango/audio_ogg_button.inc.html' %}</a>
 
<a class="feedlink" href="{{ object.mp3_path }}">{% include 'podjango/audio_mp3_button.inc.html' %}</a>
 
{{ object.title|safe }}</h2>
 

	
 
<p class="date">{{ object.pub_date|date:"j F Y" }}</p>
...
 
@@ -38,11 +40,11 @@ running time is {{ object.duration}}.</p>
 
<div class="i">
 
{% include "podjango/credits.inc.html" %}
 
{% include "podjango/feedback.inc.html" %}
 
{% include "podjango/license.inc.html" %}
 
</div>
 

	
 
{% if object.tags.all %}<p class="blog-tags">Tags: {% for tag in object.tags.all %}<a href="{{ tag.get_absolute_url }}">{{ tag.label }}</a>{% if not forloop.last %}, {% endif %}{% endfor %}</p>{% endif %}
 
{% if object.tags.all %}<p class="blog-tags">Tags: {% for tag in object.tags.all %}<a href="{% tag_url podcast tag %}">{{ tag.label }}</a>{% if not forloop.last %}, {% endif %}{% endfor %}</p>{% endif %}
 

	
 
<p><span class="continued"><a href="{% url 'podjango:cast' %}">Other episodes&hellip;</a></span></p>
 
<p><span class="continued"><a href="{% url 'podjango:cast' podcast_slug=podcast.slug %}">Other episodes&hellip;</a></span></p>
 

	
 
{% endblock %}
conservancy/podjango/templates/podjango/cast_list.html
Show inline comments
 
<!-- FIXME: SFLC specific content -->
 
<!--  Copyright (C) 2008       Bradley M. Kuhn <bkuhn@ebb.org> -->
 
<!-- Permission is granted to copy, modify, redistribute, propagate,  -->
 
<!-- and/or convey this template in any form. -->
 
{% extends "podjango/base_podcast.html" %}
 

	
 
{% block subtitle %}The Corresponding Source - {% endblock %}
 
{% load podjango %}
 

	
 
{% block header %}
 
  {{ block.super }}
 
  <link rel="alternate" type="application/rss+xml" title="MP3 Audio Feed" href="{% url 'podjango:feed-mp3' podcast_slug=podcast.slug %}" />
 
  <link rel="alternate" type="application/rss+xml" title="Ogg/Vorbis Audio Feed" href="{% url 'podjango:feed-ogg' podcast_slug=podcast.slug %}" />
 
{% endblock %}
 

	
 
{% block subtitle %}{{ podcast.title }} - {% endblock %}
 

	
 
{% block content %}
 

	
 
<h1>{% include 'podjango/feed_links.inc.html' %} <a href="{% url 'podjango:cast-home' %}">The Corresponding Source</h1>
 
<h1>{% include 'podjango/feed_links.inc.html' %} <a href="{% url 'podjango:cast-home' podcast_slug=podcast.slug %}">{{ podcast.title }}</a></h1>
 

	
 
{% if tags %}
 
<p>Displaying casts
 
tagged {% for tag in tags %}{% if not forloop.last %}{% if not forloop.first %}, {% endif %}{% endif %}<a href="{{ tag.get_absolute_url }}">{{ tag.label }}</a>{% if forloop.revcounter == 2 %} or {% endif %}{% endfor %}
 
tagged: {% for tag in tags %}{% if not forloop.last %}{% if not forloop.first %}, {% endif %}{% endif %}<a href="{% tag_url podcast tag %}">{{ tag.label }}</a>{% if forloop.revcounter == 2 %} or {% endif %}{% endfor %}
 
</p>
 
{% endif %}
 

	
 
{% for object in object_list %}
 
    <div class="pa2 mb2" style="background: #F0FFB8">
 
    <h3>
 
<a class="feedlink" href="{{ object.ogg_path }}">{% include 'podjango/audio_ogg_button.inc.html' %}</a>
 
<a class="feedlink" href="{{ object.mp3_path }}">{% include 'podjango/audio_mp3_button.inc.html' %}</a>
 

	
 
<a href="{{ object.get_absolute_url }}">{{ object.title|safe }}</a></h3>
 
<a href="{% episode_url podcast object %}">{{ object.title|safe }}</a></h3>
 
    <p class="date">{{ object.pub_date|date:"F j, Y" }}</p>
 
    <h4>Summary</h4>
 

	
 
    {{ object.summary|safe }}
 

	
 
    <p>
...
 
@@ -33,13 +41,13 @@ tagged {% for tag in tags %}{% if not forloop.last %}{% if not forloop.first %},
 
    running time is {{ object.duration}}.</p>
 
    <h4>Show Notes</h4>
 
    <div>
 
      {{ object.body|safe }}
 
    </div>
 

	
 
    {% if object.tags.all %}<p class="cast-tags small">Tags: {% for tag in object.tags.all %}<a href="{{ tag.get_absolute_url }}">{{ tag.label }}</a>{% if not forloop.last %}, {% endif %}{% endfor %}</p>{% endif %}
 
    {% if object.tags.all %}<p class="cast-tags small">Tags: {% for tag in object.tags.all %}<a href="{% tag_url podcast tag %}">{{ tag.label }}</a>{% if not forloop.last %}, {% endif %}{% endfor %}</p>{% endif %}
 
    </div>
 
{% endfor %}
 

	
 
<p>
 
{% if has_next %}<a class="next_page_button" href="?page={{ next }}{% if query_string %}&amp;{{ query_string|escape }}{% endif %}">Next page (older) &raquo;</a>{% endif %}
 
{% if has_previous %}<a href="?page={{ previous }}{% if query_string %}&amp;{{ query_string|escape }}{% endif %}">&laquo; Previous page (newer)</a>{% endif %}
conservancy/podjango/templates/podjango/feed_links.inc.html
Show inline comments
 
{% load static %}
 

	
 
<a href="{% url 'podjango:feed-ogg' %}" class="feedlink">
 
<a href="{% url 'podjango:feed-ogg' podcast_slug=podcast.slug %}" class="feedlink">
 
  <img src="{% static 'podjango/img/cast/rss-audioogg.png' %}" alt="[Ogg/Vorbis Audio RSS]"/>
 
</a>
 
<a href="{% url 'podjango:feed-mp3' %}" class="feedlink">
 
<a href="{% url 'podjango:feed-mp3' podcast_slug=podcast.slug %}" class="feedlink">
 
  <img src="{% static 'podjango/img/cast/rss-audiomp3.png' %}" alt="[MP3 Audio RSS]"/>
 
</a>
conservancy/podjango/templates/podjango/frontpage.html
Show inline comments
 
{% extends "podjango/base_podcast.html" %}
 

	
 
{% block content %}
 
{% block header %}
 
  {{ block.super }}
 
  <link rel="alternate" type="application/rss+xml" title="MP3 Audio Feed" href="{% url 'podjango:feed-mp3' podcast_slug=podcast.slug %}" />
 
  <link rel="alternate" type="application/rss+xml" title="Ogg/Vorbis Audio Feed" href="{% url 'podjango:feed-ogg' podcast_slug=podcast.slug %}" />
 
{% endblock %}
 

	
 
{% block content %}
 
<div class="singleColumn">
 

	
 
<h1>The Corresponding Source</h1>
 

	
 
<p>The Corresponding Source (formerly Free as in Freedom) is a bi-weekly oggcast, hosted and presented by
 
<a href="http://ebb.org/bkuhn">Bradley M. Kuhn</a> and <a href="http://gnomg.org">Karen Sandler</a>.
 
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.</p>
 
<h1>{{ podcast.title }}</h1>
 

	
 
{% include "podjango/credits.inc.html" %}
 
{% include "podjango/feedback.inc.html" %}
 
{{ podcast.long_description|safe }}
 

	
 
<h2>Follow the RSS and Other Feeds</h2>
 

	
 
<p>There is RSS for both <a href="{% url 'podjango:feed-ogg' %}">ogg format</a>
 
  and  <a href="{% url 'podjango:feed-mp3' %}">mp3 format</a>.<!-- These links <em>might</em>
 
  work if you want to <a href="itpc://faif.us{% url 'podjango:feed-mp3' %}">subscribe to the show</a> <a href="https://itunes.apple.com/us/podcast/free-as-in-freedom/id450458894">with proprietary Apple devices.</a>--></p>
 
<p>There is RSS for both <a href="{% url 'podjango:feed-ogg' podcast_slug=podcast.slug %}">ogg format</a>
 
  and  <a href="{% url 'podjango:feed-mp3' podcast_slug=podcast.slug %}">mp3 format</a>.<!-- These links <em>might</em>
 
  work if you want to <a href="itpc://faif.us{% url 'podjango:feed-mp3' podcast_slug=podcast.slug %}">subscribe to the show</a> <a href="https://itunes.apple.com/us/podcast/free-as-in-freedom/id450458894">with proprietary Apple devices.</a>--></p>
 

	
 
<h2>{% include 'podjango/feed_links.inc.html' %}<a href="{% url 'podjango:cast' %}">Recent Shows</a></h2>
 
<h2>{% include 'podjango/feed_links.inc.html' %}<a href="{% url 'podjango:cast' podcast_slug=podcast.slug %}">Recent Shows</a></h2>
 

	
 
{% for cc in cast %}
 
<div class="pa2 mb2" style="background: #F0FFB8">
 
<a class="feedlink" href="{{ cc.ogg_path }}">{% include 'podjango/audio_ogg_button.inc.html' %}</a>
 
<a class="feedlink" href="{{ cc.mp3_path }}">{% include 'podjango/audio_mp3_button.inc.html' %}</a>
 
<h3><a href="{{ cc.get_absolute_url }}">{{ cc.title|safe }}</a></h3>
 
<h3><a href="{% url 'podjango:detail' podcast_slug=podcast.slug year=cc.pub_date.year month=cc.month_str day=cc.pub_date.day slug=cc.slug %}">{{ cc.title|safe }}</a></h3>
 
<p class="date">{{ cc.pub_date|date:"F j, Y" }}</p>
 
{{ cc.summary|safe }}
 
</div>
 
{% endfor %}
 
<p><a href="{% url 'podjango:cast' %}">All oggcasts&hellip;</a></p>
 
<p><a href="{% url 'podjango:cast' podcast_slug=podcast.slug %}">All oggcasts&hellip;</a></p>
 
</div>
 
{% endblock %}
conservancy/podjango/templates/podjango/podcasts.html
Show inline comments
 
new file 100644
 
<!-- FIXME: SFLC specific content -->
 
<!--  Copyright (C) 2008       Bradley M. Kuhn <bkuhn@ebb.org> -->
 
<!-- Permission is granted to copy, modify, redistribute, propagate,  -->
 
<!-- and/or convey this template in any form. -->
 
{% extends "podjango/base_podcast.html" %}
 

	
 
{% load podjango %}
 

	
 
{% block subtitle %}Casts - {% endblock %}
 

	
 
{% block content %}
 

	
 
<h1>Casts</h1>
 

	
 
{% for podcast in podcasts %}
 
  <p><a href="{% url 'podjango:cast-home' podcast_slug=podcast.slug %}">{{ podcast.title }}</a></p>
 
{% endfor %}
 
{% endblock %}
conservancy/podjango/templatetags/__init__.py
Show inline comments
 
new file 100644
conservancy/podjango/templatetags/podjango.py
Show inline comments
 
new file 100644
 
from django import template
 
from django.urls import reverse
 

	
 
register = template.Library()
 

	
 

	
 
@register.simple_tag
 
def tag_url(podcast, tag):
 
    return '{base_url}?tag={tag_slug}'.format(
 
        base_url=reverse('podjango:cast', kwargs={'podcast_slug': podcast.slug}),
 
        tag_slug=tag.slug
 
    )
 

	
 

	
 
@register.simple_tag
 
def episode_url(podcast, cast):
 
    return reverse(
 
        'podjango:detail',
 
        kwargs={
 
            'podcast_slug': podcast.slug,
 
            'year': cast.pub_date.year,
 
            'month': cast.pub_date.strftime('%b').lower(),
 
            'day': cast.pub_date.day,
 
            'slug': cast.slug,
 
        }
 
    )
conservancy/podjango/urls.py
Show inline comments
...
 
@@ -17,45 +17,60 @@
 
# along with this program in a file in the toplevel directory called
 
# "AGPLv3".  If not, see <http://www.gnu.org/licenses/>.
 

	
 
import datetime
 

	
 
from django.conf import settings
 
from django.shortcuts import get_object_or_404
 
from django.urls import path
 
from django.views.generic.dates import (
 
    DateDetailView,
 
    DayArchiveView,
 
    MonthArchiveView,
 
    YearArchiveView,
 
)
 
from django.views.generic.dates import DateDetailView
 

	
 
from . import frontpage
 
from .feeds import Mp3CastFeed, OggCastFeed, view
 
from .models import Cast, CastTag
 
from .views import custom_index, query
 
from .models import Cast, CastTag, Podcast
 
from . import views
 

	
 
app_name = 'podjango'
 

	
 
extra_context = {}
 
info_dict = {
 
    'queryset': Cast.objects.all(),
 
    'date_field': 'pub_date',
 
    'extra_context': extra_context,
 
    'template_name': 'podjango/cast/cast_detail.html',
 
}
 

	
 
class PodcastDateDetailView(DateDetailView):
 
    date_field = 'pub_date'
 
    model = Cast
 

	
 
    def get(self, request, podcast_slug, *args, **kwargs):
 
        self.podcast = get_object_or_404(Podcast, slug=podcast_slug)
 
        return super().get(request, *args, **kwargs)
 

	
 
    def get_queryset(self):
 
        return super().get_queryset().filter(podcast=self.podcast)
 

	
 
    def get_context_data(self, **kwargs):
 
        context = super().get_context_data(**kwargs)
 
        context['podcast'] = self.podcast
 
        return context
 

	
 

	
 
urlpatterns = [
 
    path('', frontpage.view, name='cast-home'),
 
    path('<int:year>/<month>/<int:day>/<slug:slug>/', DateDetailView.as_view(**info_dict), name='detail'),
 
    path('<int:year>/<month>/<int:day>/', DayArchiveView.as_view(**info_dict), name='day-archive'),
 
    path('<int:year>/<month>/', MonthArchiveView.as_view(**info_dict), name='month-archive'),
 
    path('<int:year>/', YearArchiveView.as_view(**info_dict), name='year-archive'),
 
    path('all/', custom_index, dict(info_dict, paginate_by=20), name='cast'),
 
    path('feeds/ogg/', OggCastFeed(), name='feed-ogg'),
 
    path('feeds/mp3/', Mp3CastFeed(), name='feed-mp3'),
 
    path('feeds/', view, name='feeds'),
 
    path('', views.podcasts, name='podcasts'),
 
    path('<slug:podcast_slug>/', frontpage.view, name='cast-home'),
 
    path(
 
        '<slug:podcast_slug>/<int:year>/<month>/<int:day>/<slug:slug>/',
 
        PodcastDateDetailView.as_view(
 
            template_name='podjango/cast_detail.html',
 
        ),
 
        name='detail'
 
    ),
 
    path('<slug:podcast_slug>/all/', views.custom_index, info_dict, name='cast'),
 
    path('<slug:podcast_slug>/feeds/ogg/', OggCastFeed(), name='feed-ogg'),
 
    path('<slug:podcast_slug>/feeds/mp3/', Mp3CastFeed(), name='feed-mp3'),
 
    path('<slug:podcast_slug>/feeds/', view, name='feeds'),
 
]
 

	
 
if settings.DEBUG:
 
    from django.conf.urls.static import static
 
    urlpatterns += static('/', document_root='podjango/static')
 

	
conservancy/podjango/views.py
Show inline comments
...
 
@@ -19,59 +19,61 @@
 
from datetime import datetime
 
from functools import reduce
 
from operator import or_
 

	
 
from django.shortcuts import get_object_or_404, render
 

	
 
from .models import CastTag
 
from .models import Cast, CastTag, Podcast
 

	
 

	
 
def podcasts(request):
 
    podcasts = Podcast.objects.all()
 
    return render(request, 'podjango/podcasts.html', {'podcasts': podcasts})
 

	
 

	
 
def OR_filter(field_name, objs):
 
    from django.db.models import Q
 
    return reduce(or_,
 
                  [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):
 
def custom_index(request, podcast_slug, *args, **kwargs):
 
    """Cast list view that allows scrolling and also shows an index by
 
    year.
 
    """
 

	
 
    podcast = get_object_or_404(Podcast, slug=podcast_slug)
 
    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))
 
        queryset = Cast.objects.filter(
 
            podcast=podcast,
 
            **{'%s__lte' % date_field: datetime.now()},
 
        )
 

	
 
    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))
 
        queryset = queryset.filter(OR_filter('tags', tags), podcast=podcast)
 

	
 
    if authors or tags:
 
        query_string = '&'.join(['author=%s' % a.username for a in authors]
 
                                + ['tag=%s' % t.slug for t in tags])
 
    if tags:
 
        query_string = '&'.join('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})
 
    return render(request, 'podjango/cast_list.html', {'podcast': podcast,
 
                                                       'object_list': queryset,
 
                                                       'tags': tags})
conservancy/templates/submenus/learn_partial.html
Show inline comments
 
<ul>
 
  <li><a href="{% url 'podjango:cast-home' %}">The Corresponding Source</a></li>
 
  <li><a href="{% url 'podjango:cast-home' podcast_slug='the-corresponding-source' %}">The Corresponding Source</a></li>
 
  <li><a href="/copyleft-compliance/glossary.html">Glossary of Terms</a></li>
 
  <li><a href="/copyleft-compliance/vizio.html">Vizio Lawsuit</a></li>
 
  <li><a href="/press/qanda.html">Q&amp;A</a></li>
 
  <li><a class="IssuesInTheNews" href="/press/inthenews.html">Our Issues In The News</a></li>
 
</ul>
conservancy/templates/submenus/what_we_do_partial.html
Show inline comments
 
<ul>
 
  <li><a href="{% url 'podjango:cast-home' %}">The Corresponding Source</a></li>
 
  <li><a href="{% url 'podjango:cast-home' podcast_slug='the-corresponding-source' %}">The Corresponding Source</a></li>
 
  <li class="CopyleftCompliance"><a href="/copyleft-compliance/">Copyleft Compliance</a></li>
 
  <li class="VizioTopBar"><a href="/copyleft-compliance/vizio.html">Vizio Lawsuit</a></li>
 
  <li class="FIXME"><a href="/projects/">Member Projects</a></li>
 
  <li class="Outreachy"><a href="https://outreachy.org">Outreachy</a></li>
 
  <li class="UseTheSource"><a href="{% url 'usethesource:landing' %}">Use The Source</a></li>
 
  <li class="FOSSY"><a href="/fossy/">FOSSY</a></li>
conservancy/urls.py
Show inline comments
...
 
@@ -26,13 +26,13 @@ from . import views as static_views
 

	
 
urlpatterns = [
 
    path('', frontpage.view),
 
    path('admin/', admin.site.urls),
 
    path('assignment/', include('conservancy.assignment.urls')),
 
    path('blog/', include('conservancy.blog.urls')),
 
    path('casts/the-corresponding-source/', include('conservancy.podjango.urls')),
 
    path('casts/', include('conservancy.podjango.urls')),
 
    path('contacts/', include('conservancy.contacts.urls')),
 
    path('contractpatch/', include('conservancy.contractpatch.urls')),
 
    path('feeds/', feeds.view),
 
    path('feeds/blog/', feeds.BlogFeed()),
 
    path('feeds/news/', feeds.PressReleaseFeed()),
 
    path('feeds/omnibus/', feeds.OmnibusFeed()),
0 comments (0 inline, 0 general)