├── README.rst ├── setup.py ├── .gitignore └── podcast_feed └── __init__.py /README.rst: -------------------------------------------------------------------------------- 1 | django-podcast-feed 2 | =================== 3 | 4 | Extra attributes or methods to set on ``PodcastFeed``: 5 | 6 | - ``subtitle`` 7 | - ``artwork_link`` 8 | - ``itunes_category`` 9 | - ``explicit`` 10 | - ``owner_name`` 11 | - ``owner_email`` 12 | - ``item_duration`` 13 | 14 | For example:: 15 | 16 | MyPodcastFeed(podcast_feed.PodcastFeed): 17 | artwork_link = static('artwork.png') 18 | 19 | def get_items(self): 20 | Episode.objects.all() 21 | 22 | def item_duration(self, item): 23 | return item.duration_field 24 | 25 | A more involved usage can be found in `jeffbr13/br-rss's views `_. 26 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | from codecs import open 3 | 4 | from podcast_feed import VERSION, NAME, DESCRIPTION 5 | 6 | with open('README.rst', encoding='utf-8') as f: 7 | long_description = f.read() 8 | 9 | setup( 10 | name=NAME, 11 | description=DESCRIPTION, 12 | long_description=long_description, 13 | keywords='django podcast feed', 14 | version=VERSION, 15 | license='MPL 2.0', 16 | 17 | author='Ben Jeffrey', 18 | author_email='mail@benjeffrey.net', 19 | url='https://github.com/jeffbr13/xq', 20 | 21 | classifiers=[ 22 | 'Development Status :: 3 - Alpha', 23 | 'Programming Language :: Python :: 3', 24 | 'Intended Audience :: Developers', 25 | 'License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)', 26 | 'Framework :: Django', 27 | ], 28 | 29 | packages=find_packages(), 30 | 31 | install_requires=[ 32 | 'django' 33 | ], 34 | ) 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Python template 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | .hypothesis/ 50 | 51 | # Translations 52 | *.mo 53 | *.pot 54 | 55 | # Django stuff: 56 | *.log 57 | .static_storage/ 58 | .media/ 59 | local_settings.py 60 | 61 | # Flask stuff: 62 | instance/ 63 | .webassets-cache 64 | 65 | # Scrapy stuff: 66 | .scrapy 67 | 68 | # Sphinx documentation 69 | docs/_build/ 70 | 71 | # PyBuilder 72 | target/ 73 | 74 | # Jupyter Notebook 75 | .ipynb_checkpoints 76 | 77 | # pyenv 78 | .python-version 79 | 80 | # celery beat schedule file 81 | celerybeat-schedule 82 | 83 | # SageMath parsed files 84 | *.sage.py 85 | 86 | # Environments 87 | .env 88 | .venv 89 | env/ 90 | venv/ 91 | ENV/ 92 | env.bak/ 93 | venv.bak/ 94 | 95 | # Spyder project settings 96 | .spyderproject 97 | .spyproject 98 | 99 | # Rope project settings 100 | .ropeproject 101 | 102 | # mkdocs documentation 103 | /site 104 | 105 | # mypy 106 | .mypy_cache/ 107 | 108 | -------------------------------------------------------------------------------- /podcast_feed/__init__.py: -------------------------------------------------------------------------------- 1 | from django.contrib.syndication.views import Feed 2 | from django.utils.feedgenerator import Rss201rev2Feed 3 | 4 | VERSION = '0.0.2' 5 | NAME = 'django-podcast-feed' 6 | DESCRIPTION = 'Generate Apple Podcasts-compatible syndication feeds.' 7 | 8 | 9 | class ItunesPodcastRssFeed(Rss201rev2Feed): 10 | def __init__(self, title, link, description, language=None, author_email=None, 11 | author_name=None, author_link=None, subtitle=None, categories=None, 12 | feed_url=None, feed_copyright=None, feed_guid=None, ttl=None, 13 | artwork_link=None, category=None, explicit=None, owner_name=None, owner_email=None, 14 | **kwargs): 15 | super().__init__(title, link, description, language, author_email, 16 | author_name, author_link, subtitle, categories, 17 | feed_url, feed_copyright, feed_guid, ttl, **kwargs) 18 | self.feed['artwork_link'] = artwork_link 19 | self.feed['itunes_category'] = category 20 | self.feed['explicit'] = explicit 21 | self.feed['owner_name'] = owner_name 22 | self.feed['owner_email'] = owner_email 23 | 24 | def root_attributes(self): 25 | attrs = super().root_attributes() 26 | attrs['xmlns:itunes'] = 'http://www.itunes.com/dtds/podcast-1.0.dtd' 27 | return attrs 28 | 29 | def add_root_elements(self, handler): 30 | super().add_root_elements(handler) 31 | handler.addQuickElement('itunes:image', attrs={'href': self.feed.get('artwork_link', '')}) 32 | handler.addQuickElement('itunes:subtitle', self.feed.get('subtitle', '')) 33 | handler.addQuickElement('itunes:summary', self.feed.get('description', '')) 34 | handler.addQuickElement('itunes:category', attrs={'text': self.feed.get('itunes_category', '')}) 35 | handler.addQuickElement('itunes:keywords', ', '.join(self.feed.get('categories', []))) 36 | handler.addQuickElement('itunes:explicit', 'yes' if self.feed.get('explicit') else 'clean') 37 | handler.addQuickElement('itunes:author', self.feed.get('author_name')) 38 | 39 | handler.startElement('itunes:owner', {}) 40 | handler.addQuickElement('itunes:name', self.feed.get('owner_name')) 41 | handler.addQuickElement('itunes:email', self.feed.get('owner_email')) 42 | handler.endElement('itunes:owner') 43 | 44 | def add_item_elements(self, handler, item): 45 | super().add_item_elements(handler, item) 46 | if item.get('description'): 47 | handler.addQuickElement('itunes:summary', item.get('description')) 48 | if item.get('author_name'): 49 | handler.addQuickElement('itunes:author', item.get('author_name')) 50 | if item.get('duration'): 51 | handler.addQuickElement('itunes:duration', item.get('duration')) 52 | 53 | 54 | class PodcastFeed(Feed): 55 | """Custom Feed subclass for Apple Podcasts format. 56 | 57 | Set the following attributes on your subclass: 58 | 59 | - artwork_link 60 | - itunes_category 61 | - explicit 62 | - owner_name 63 | - owner_email 64 | """ 65 | feed_type = ItunesPodcastRssFeed 66 | 67 | def get_feed(self, obj, request): 68 | feed = super().get_feed(obj, request) 69 | feed.feed['artwork_link'] = self._get_dynamic_attr('artwork_link', obj) 70 | feed.feed['itunes_category'] = self._get_dynamic_attr('itunes_category', obj) 71 | feed.feed['explicit'] = self._get_dynamic_attr('explicit', obj) 72 | feed.feed['owner_name'] = self._get_dynamic_attr('owner_name', obj) 73 | feed.feed['owner_email'] = self._get_dynamic_attr('owner_email', obj) 74 | return feed 75 | 76 | def item_extra_kwargs(self, item): 77 | extra_kwargs = super().item_extra_kwargs(item) 78 | extra_kwargs['duration'] = str(self._get_dynamic_attr('item_duration', item)) 79 | return extra_kwargs 80 | --------------------------------------------------------------------------------