diff --git a/TODO.md b/TODO.md index 9b7599516d32795ca1ce78c3d00b2ebacd1fe11c..551eb23a7d6daddf2a32ae9a668de259ece6e71a 100644 --- a/TODO.md +++ b/TODO.md @@ -1,5 +1,7 @@ # To-do +* remove `ForceCanonicalHostnameMiddleware` by ensuring canonical redirect and HTTPS redirect is done by Apache +* serve a 400 in Apache for a hostname we don't explicitly support * use `` elements for supporter page hidden sections, rather than complex jQuery - or consider Alpine.js * replace `internalNavigate` with inline flexbox layout * migrate to Django 4.2 diff --git a/conservancy/podjango/admin.py b/conservancy/podjango/admin.py index aa7766209baf55d5f4c48019e376592fa4ca6a2f..0fdb0e92bdba255bbc545a25a37f388043cd5d32 100644 --- a/conservancy/podjango/admin.py +++ b/conservancy/podjango/admin.py @@ -24,8 +24,10 @@ from .models import Cast, CastTag 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'] diff --git a/conservancy/podjango/feeds.py b/conservancy/podjango/feeds.py index 7c4b84e463bb880844a53c022ad7bcd354bb258d..601434fe572fc05f3d157eb4e2ae42e3cb5bbd0a 100644 --- a/conservancy/podjango/feeds.py +++ b/conservancy/podjango/feeds.py @@ -18,10 +18,7 @@ # from datetime import datetime -import itertools -import operator -from django.conf import settings from django.contrib.sites.shortcuts import get_current_site from django.contrib.syndication.views import Feed, add_domain from django.shortcuts import render @@ -53,36 +50,43 @@ class CastFeedBase(Feed): if hasattr(getattr(item, attr), 'year'): year = getattr(getattr(item, attr), 'year') break - return { 'year' : year } + 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' : 'The Corresponding Source (formerly Free as in Freedom)', - 'link' : self.author_link, - 'type' : 'video/jpg'}, - 'category' : { 'name' : 'Government & Organizations', 'scheme' : 'http://www.itunes.com/dtds/podcast-1.0.dtd', - 'subcats' : [ 'Non-Profit' ] }, - 'keywords' : 'open source, opensource, freesoftware, software freedom, legal, law, linux, free, license, gpl, lgpl, agpl, bsd', - 'iTunesAuthor' : 'Software Freedom Conservancy', - 'iTunesSubtitle' : 'Bi-Weekly Discussion of Legal, Policy, and Any other Issues in the Free, Libre, and Open Source Software (FLOSS) Community', - 'copyrightHolder' : self.copyright_holder(), - 'copyrightLicense' : self.license_no_html() } + 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': 'The Corresponding Source (formerly Free as in Freedom)', + 'link': self.author_link, + 'type': 'video/jpg'}, + 'category': {'name': 'Government & Organizations', 'scheme': 'http://www.itunes.com/dtds/podcast-1.0.dtd', + 'subcats': ['Non-Profit']}, + 'keywords': 'open source, opensource, freesoftware, software freedom, legal, law, linux, free, license, gpl, lgpl, agpl, bsd', + 'iTunesAuthor': 'Software Freedom Conservancy', + 'iTunesSubtitle': 'Bi-Weekly Discussion of Legal, Policy, and Any other Issues in the Free, Libre, and Open Source Software (FLOSS) Community', + 'copyrightHolder': self.copyright_holder(), + 'copyrightLicense': self.license_no_html(), + } + def for_podcast_item_extra_kwargs(self, item): - return { 'duration' : item.duration, - 'year' : item.date_created.year, - 'dcCreator' : 'oggcast@faif.us (Bradley and Karen)', - 'intheitembkuhn' : item.__dict__.__str__()} + 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'] + handler.addQuickElement('managingEditor', self.feed['author_email'] + ' (' + self.feed['managingEditorNames'] + ')') handler.startElement('image', {}) handler.addQuickElement('url', self.feed['rssImage']['url']) @@ -91,54 +95,55 @@ def podcast_helper_add_root_elements(self, handler): 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']}) + {'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']}) + handler.startElement("itunes:category", {'text': self.feed['category']['name']}) for cc in self.feed['category']['subcats']: - handler.addQuickElement("itunes:category", '', { 'text': cc }) + 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"}) - + 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']) @@ -147,9 +152,9 @@ def podcast_helper_add_item_elements(self, handler, item): 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}) + handler.addQuickElement('media:content', '', {'url': item['enclosure'].url, + 'fileSize': item['enclosure'].length, + 'type': item['enclosure'].mime_type}) # http://www.feedforall.com/itune-tutorial-tags.htm # http://www.feedforall.com/mediarss.htm @@ -210,7 +215,7 @@ class CastFeed(CastFeedBase): return "http://faif.us" def item_categories(self, item): - return ('Technology',) + return ('Technology',) def copyright_holder(self): return "Software Freedom Conservancy" @@ -222,7 +227,7 @@ class CastFeed(CastFeedBase): def item_extra_kwargs(self, item): return for_podcast_item_extra_kwargs(self, item) -# FIXME: +# FIXME: # GUEST NAME GOES HERE!!! # # If applicable, at the item level, this tag can contain information @@ -236,6 +241,7 @@ class Mp3CastFeed(CastFeed): 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): @@ -243,6 +249,7 @@ class OggCastFeed(CastFeed): def item_enclosure_length(self, item): return item.ogg_length + feed_dict = { 'cast-ogg': OggCastFeed, 'cast-mp3': Mp3CastFeed, @@ -252,6 +259,7 @@ feed_dict = { for k, v in feed_dict.items(): v.get_absolute_url = '/feeds/%s/' % k + def view(request): """Listing of all available feeds """ diff --git a/conservancy/podjango/frontpage.py b/conservancy/podjango/frontpage.py index 68604dde07daef776bb9275cfd4f1089c6856f6e..d21dec32f00764ee31091c9930438f4bbd8b661a 100644 --- a/conservancy/podjango/frontpage.py +++ b/conservancy/podjango/frontpage.py @@ -17,7 +17,7 @@ # along with this program in a file in the toplevel directory called # "AGPLv3". If not, see . -from datetime import datetime, timedelta +from datetime import datetime from django.shortcuts import render diff --git a/conservancy/podjango/models.py b/conservancy/podjango/models.py index 30b82d3d48f1b8dd36e18f302cc96fc25df564fc..edfc8c5b2b7038f616108a6f977df66a74d9967d 100644 --- a/conservancy/podjango/models.py +++ b/conservancy/podjango/models.py @@ -53,10 +53,12 @@ class Cast(models.Model): 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() 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_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") diff --git a/conservancy/podjango/templatetags/date_within.py b/conservancy/podjango/templatetags/date_within.py index 207bca71c7f74095ea29581ad2809219711ee6cf..f0921170d5ad99d863073f48b5d904b18c756534 100644 --- a/conservancy/podjango/templatetags/date_within.py +++ b/conservancy/podjango/templatetags/date_within.py @@ -4,6 +4,7 @@ from django import template register = template.Library() + @register.filter def date_within_past_days(value, arg): # question: does datetime.now() do a syscall each time is it called? diff --git a/conservancy/podjango/urls.py b/conservancy/podjango/urls.py index 5c408e1a0fd40035960c8558d5efa284bdb47bfd..31165d124c63a84e34e4cfdce67d7b87003c0419 100644 --- a/conservancy/podjango/urls.py +++ b/conservancy/podjango/urls.py @@ -17,10 +17,10 @@ # along with this program in a file in the toplevel directory called # "AGPLv3". If not, see . +import datetime + from django.conf import settings -from django.conf.urls import include, url -from django.contrib import admin -from django.contrib.syndication.views import Feed +from django.conf.urls import url from django.views.generic.dates import ( DateDetailView, DayArchiveView, @@ -29,7 +29,7 @@ from django.views.generic.dates import ( ) from . import frontpage -from .feeds import Mp3CastFeed, OggCastFeed, feed_dict, view +from .feeds import Mp3CastFeed, OggCastFeed, view from .models import Cast, CastTag from .views import custom_index, query @@ -60,6 +60,7 @@ if settings.DEBUG: from django.conf.urls.static import static urlpatterns += static('/', document_root='podjango/static') + def all_tags_by_use_amount(): """Returns all tags with an added 'cnt' attribute (how many times used) @@ -85,6 +86,7 @@ def all_tags_by_use_amount(): 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 diff --git a/conservancy/podjango/views.py b/conservancy/podjango/views.py index 28e7ffa902311af9b5a115759987a75574c51e57..ae81663db3bd052fc5141672860717e6c65b7444 100644 --- a/conservancy/podjango/views.py +++ b/conservancy/podjango/views.py @@ -21,9 +21,8 @@ from functools import reduce from operator import or_ from django.shortcuts import get_object_or_404, render -from django.views.generic.list import ListView -from .models import Cast, CastTag +from .models import CastTag def OR_filter(field_name, objs): @@ -31,9 +30,11 @@ def OR_filter(field_name, objs): 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): """Cast list view that allows scrolling and also shows an index by year. @@ -75,6 +76,7 @@ def custom_index(request, queryset, *args, **kwargs): # TODO return render(request, 'podjango/cast/cast_list.html', {'object_list': queryset}) + def query(request): """Page to query the cast based on and tags """ @@ -82,15 +84,15 @@ def query(request): if request.GET: d = request.GET.copy() if 'authors' in d.getlist('all'): - d.setlist('author', []) # remove author queries + 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 + 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 + d.setlist('rss', []) # remove it query_string = d.urlencode() @@ -100,6 +102,7 @@ def query(request): 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