我尝试使用django使用feedgenerator.Rss201rev2Feed创建播客RSS Feed 作为Feed生成器,它在某种程度上工作opposite to BeautifulSoup:将信息放入适当的xml标记
它运作良好,除了我不想逃脱所有 html
特别是,我希望rss Feed的<itunes:summary>
值显示如下:
<itunes:summary><![CDATA[Link to <a href="http://www.website.com">the website</a>]]></itunes:summary>
根据{{3}}
如果我在普通视图中渲染html,我可以在html模板中使用Apple spec。我现在需要类似的东西,有选择地阻止<
在RSS Feed中被转义。
也就是说,我需要rss与<![CDATA[...]]
一起显示,而不是转发到<![CDATA[...]]>
然而,似乎Django&#34; Django会自动在RSS提要(或任何XML)中自动显示特殊字符,无论你是否将其通过安全过滤器而不是&#34; (见|safe
filter)
到目前为止没有运气:
因此,到目前为止尝试使用this 2009 question已经证明是无用的。
我还不确定如何解释mark_safe以将#34; autoescape = False传递给django.contrib.syndication.feeds&#34;中的render()调用。
将, escape=False
添加到addQuickElement注释中的建议返回了错误
handler.addQuickElement(u'itunes:summary',item['summary'], escape=False)
TypeError: addQuickElement() got an unexpected keyword argument 'escape'
它是one idea,但到目前为止我找不到任何解决方案。
有人知道让<![CDATA[...
出现在最终Feed中的简洁方法,而不是转发到<![CDATA[...
吗?
修改: 这是当前形式的代码,就像我发布这个问题时一样(我还没有尝试合并@Lego&#39的回答)
import mimetypes
from django.conf import settings
from django.contrib.syndication.views import Feed
# For customising the feed
from django.utils.feedgenerator import Rss201rev2Feed
from django.utils import feedgenerator
# see also https://github.com/blancltd/blanc-basic-podcast/blob/master/blanc_basic_podcast/podcast/itunesfeed.py
# and https://github.com/aneumeier/feeds/blob/master/feeds/rss.py
# and https://github.com/blancltd/blanc-basic-podcast/blob/master/blanc_basic_podcast/podcast/feeds.py
# and https://docs.djangoproject.com/en/1.7/ref/contrib/syndication/#custom-feed-generators
from django.contrib.auth.models import User
from django.shortcuts import get_object_or_404
from django.utils.translation import ugettext_lazy as _
from django.contrib.sites.models import Site
from audiotracks.models import get_track_model, Playlist
Track = get_track_model()
ITEMS_PER_FEED = getattr(settings, 'AUDIOTRACKS_PODCAST_LIMIT', 99)
# MarkAdded @ToDo revisit that default maximum num. tracks per feed
from django.core.urlresolvers import reverse, reverse_lazy
from django_slack import slack_message
######################################################################
##### try adapting code from https://github.com/CaptainHayashi/django-lass-uryplayer/blob/master/uryplayer/feeds.py
from django.utils.feedgenerator import Rss201rev2Feed
from django.contrib.syndication.views import Feed
from django.contrib.sites.models import Site
from django.db.models import permalink
# from uryplayer.models import Podcast
import datetime
# MarkAdded in attempt to have un-escaped <![CDATA[...]]
from django.utils.safestring import mark_safe
# from https://stackoverflow.com/questions/275174/how-do-i-perform-html-decoding-encoding-using-python-django
try:
from html.parser import HTMLParser # py3
except ImportError:
from HTMLParser import HTMLParser # py2
unescape = HTMLParser().unescape
# print(unescape(">"))
# That proved useless so far
class iTunesPodcastsFeedGenerator(Rss201rev2Feed):
def rss_attributes(self):
return {u"version": self._version, u"xmlns:atom": u"http://www.w3.org/2005/Atom", u'xmlns:itunes': u'http://www.itunes.com/dtds/podcast-1.0.dtd'}
def add_root_elements(self, handler):
super(iTunesPodcastsFeedGenerator, self).add_root_elements(handler)
handler.addQuickElement(u'itunes:subtitle', self.feed['subtitle'])
handler.addQuickElement(u'itunes:author', self.feed['author_name'])
# handler.addQuickElement(u'itunes:summary', mark_safe(self.feed['description']))
handler.addQuickElement(u'itunes:summary', unescape(mark_safe(self.feed['description'])))
# handler.addQuickElement(u'itunes:image', self.feed['iTunes_image_url'])
handler.addQuickElement('itunes:image', '' , { 'href' : self.feed['iTunes_image_url']})
# that's from https://gitorious.org/podjango/podjango/commit/621857be0a3d7c44f1925c7daf471c38ea62c180?diffmode=sidebyside
handler.addQuickElement(u'itunes:explicit', self.feed['iTunes_explicit'])
handler.startElement(u"itunes:owner", {})
handler.addQuickElement(u'itunes:name', self.feed['iTunes_name'])
handler.addQuickElement(u'itunes:email', self.feed['iTunes_email'])
handler.endElement(u"itunes:owner")
# @ToDo: add categories
def add_item_elements(self, handler, item):
super(iTunesPodcastsFeedGenerator, self).add_item_elements(handler, item)
handler.addQuickElement(u'itunes:summary', unescape(item['summary']))
handler.addQuickElement(u'itunes:explicit',item['explicit'])
# handler.addQuickElement(u'itunes:image', item['iTunes_image_url'])
handler.addQuickElement(u'itunes:image', '' , { 'href' : self.feed['iTunes_image_url']})
# def __unicode__(self):
# return unicode(self.order_num)
class iTunesPodcastPost():
def __init__(self, podcast): # note: podcast here = Track for me
self.id = podcast.id
# self.date_submitted = podcast.date_submitted
self.pub_date = podcast.pub_date
self.title = podcast.title if podcast.title else "Track"
self.summary = unescape(podcast.description) if podcast.description else "Cool thing"
# self.description = mark_safe("<![CDATA[%s]]>" % (podcast.description)) if podcast.description else ""
self.description = podcast.description if podcast.description else "Cool thing"
self.enclosure_url = podcast.awe_url # defined in models.py
self.enclosure_length = podcast.size if podcast.size else 1 # u'unkown duration'
self.enclosure_mime_type = u'audio/mpeg' # @ToDo generalise once we have other types
self.explicit = u'yes' if podcast.explicit else u'no'
self.url = podcast.get_absolute_url
self.iTunes_image_url = podcast.main_image_url # if podcast.main_image_url else 'http://fun.com'
self.length = podcast.time_duration if podcast.time_duration else 11 # "TBD"
self.user_id = podcast.user_id
self.user = User.objects.get(id = podcast.user_id)
self.slug = podcast.slug
self.duration = podcast.time_duration if podcast.time_duration else "5:00" # "Duration TBC"
# if categories:
# categories = [to_unicode(c) for c in categories]
# see https://docs.djangoproject.com/en/1.7/_modules/django/utils/feedgenerator/#SyndicationFeed.add_root_elements
def __unicode__(self):
return self.title
def get_absolute_url(self):
# return "%s" % self.url()
# user = User.objects.get(id=self.user_id)
return reverse('track_detail', args=[self.user.username, self.slug])
class iTunesPodcastsFeed(Feed):
"""
A feed of podcasts for iTunes and other compatible podcatchers.
Based on https://github.com/CaptainHayashi/django-lass-uryplayer/blob/master/uryplayer/feeds.py
"""
def get_object(self, request, username, playlist_slug):
self.request = request
# return get_object_or_404(User, username=username)
user = get_object_or_404(User, username=username)
return get_object_or_404(Playlist, user_id=user.id, slug=playlist_slug)
def link(self, playlist):
# return self.request.build_absolute_uri("/@%s/" % user.username)
user = User.objects.get(id=playlist.user_id)
return reverse('playlist_index', args=[user.username, playlist.slug])
def title(self, playlist):
return playlist.title
# description_template = mark_safe("defaults/playlist_description_missing.html")
# not using that
def description(self, playlist):
if playlist.description:
return playlist.description
# return mark_safe("<![CDATA[%s]]>" % (playlist.description))
# No, I won't wrap in CDATA until I can avoid escaping the "<" signs here
else:
return "[Auto text] The creator has not written a description."
# return render_to_string("defaults/playlist_description_missing.txt")
# pass
def iTunes_image_url(self, obj): # TypeError: coercing to Unicode: need string or buffer, instancemethod found
if obj.main_image_url:
return unicode(obj.main_image_url) # obj.main_image_url
else:
return u'https://dl.dropboxusercontent.com/u/16441973/publicstatic/img/playlist-icon.png'
# author_name = 'University Radio York'
# modified from https://github.com/aneumeier/feeds/blob/master/feeds/rss.py
def author_name(self, obj): # obj = playlist
"""
Return the author for this feed.
The feed is in `obj`, provided by `get_object`
"""
if obj.author:
return u"%s" % obj.author
else:
return 'Playlist created by %s' % (obj.user.username)
def subtitle(self, obj): # obj = playlist
"""
Return the author for this feed.
The feed is in `obj`, provided by `get_object`
"""
if obj.subtitle:
return u"%s" % obj.author
else:
return '%s created in 2015' % (obj.title)
# def summary(self, obj):
# return obj.description
# @ToDo: finish adapting rest of this from the hard-coded URY values to actual values for my implementation
iTunes_name = u'Hard-coded iTunes name for now'
iTunes_email = u'm@rkmoriarty.com'
# @ToDo: make dynamic, not hard-coded
iTunes_explicit = u'no'
feed_type = iTunesPodcastsFeedGenerator
feed_copyright = "Copyright 1967-%s University Radio York" % datetime.date.today().year
def feed_extra_kwargs(self, playlist):
extra = {}
extra['iTunes_name'] = self.iTunes_name
extra['iTunes_email'] = self.iTunes_email
# extra['iTunes_image_url'] = self.iTunes_image_url
def get_image(self, playlist):
if playlist.main_image_url:
return playlist.main_image_url
else:
return "https://dl.dropboxusercontent.com/u/16441973/publicstatic/img/rss_playlist_icon_placeholder.png"
# @ToDo: replace with Awesound logo
# return render_to_string("defaults/playlist_description_missing.txt")
# pass
extra['iTunes_image_url'] = get_image(self, playlist)
extra['iTunes_explicit'] = self.iTunes_explicit
return extra
def items(self, playlist):
"""
Returns a list of items to publish in this feed.
"""
posts = playlist.tracks.all().order_by('-pub_date').order_by("-created_at")[:99]
posts = [iTunesPodcastPost(item) for item in posts]
return posts
def item_extra_kwargs(self, item):
return {'summary':unescape(mark_safe(item.description)),
'explicit':item.explicit,
'iTunes_image_url':item.iTunes_image_url}
# was summary: item.summary
# MarkAdded
def item_link(self, item):
# return item.enclosure_length
if item.user_id:
# we have a normal track created by a user
# user = User.objects.get(id = item.user_id)
return reverse('track_detail', args=[item.user.username, item.slug])
else:
# we have a funny track without a user, e.g., created via command line
return 'Exception:TrackWithoutUser'
def item_pubdate(self, item):
return item.pub_date
def item_enclosure_url(self, item):
return item.enclosure_url
def item_enclosure_length(self, item):
# return item.enclosure_length
return item.length
def item_enclosure_mime_type(self, item):
# return item.enclosure_mime_type
return 'audio/mpeg' # @ToDo: make dynamic
def item_description(self, item):
# return item.summary
if item.description:
return unescape(mark_safe(item.description))
else:
return "User has not written a description. This is an automatic message"
# current_site = Site.objects.get_current()
current_site = 'https://greatsite.com'
iTunes_feed = iTunesPodcastsFeed()
### the above will be called if both username and playlist_slug are deteced in the url
### there are two older methods to handle other situations
class AllTracks(Feed):
#
# working old method, not relevant to html escaping question
#
class UserTracks(AllTracks):
#
# working old method, not relevant to my question
#
all_tracks = AllTracks()
user_tracks = UserTracks()
### note, both of those are also subject to full html escaping also
def choose_feed(request, *args, **kwargs):
"""
Pick up the user feed or the global feed depending on whether or not the
URL contains a username parameter
"""
# feed = user_tracks if 'username' in kwargs else all_tracks
if 'username' in kwargs:
if 'playlist_slug' in kwargs:
# feed = podcast_feed
slug = kwargs['playlist_slug']
feed = iTunes_feed
if request.user:
user = request.user
slack_message('slackmessages/playlist_feed.slack', { #django_slack/slackmessages/
'playlist': Playlist.objects.get(slug=slug),
'user':user,
})
else:
feed = user_tracks
else:
feed = all_tracks
return feed.__call__(request, *args, **kwargs)
答案 0 :(得分:1)
因此,基于文档handler
是XMLGenerator
,并且调用addQuickElement
假设所有内容都是字符数据。因此,为什么它被逃脱。
您可能需要做的是覆盖SyndicationFeed.add_item_elements(self, handler, item)
并使用a
插入addQuickElement
元素,并使用startElement
and endElement
添加itunes:summary
代码
class iTunesFeed(Rss201rev2Feed):
def add_item_elements(self, handler item):
super(iTunesFeed, self).add_root_elements(handler)
handler.startElement('itunes:summary')
handler.characters('Link to ')
handler.addQuickElement('a', 'the website', {'href':'http://www.website.com'})
handler.endElement('itunes:summary')
这可能不是100%功能,但应该让你非常接近。
答案 1 :(得分:0)
我在Django 1.10中遇到了同样的问题,并将其追溯到所有逃逸发生的地步。 <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<span>Selected : <span id="selected">no selection</span></span><br>
<div class="row clickable" data-id="row1">
<div class="button clickable" data-id="btn1">
<h2>Button 1</h2>
</div>
<div class="button clickable" data-id="btn2">
<h2>Button 2</h2>
</div>
<div class="button clickable" data-id="btn3">
<h2>Button 3</h2>
</div>
</div>
<br>
<div class="row clickable" data-id="row2">
<div class="button clickable" data-id="btn1">
<h2>Button 1</h2>
</div>
<div class="button clickable" data-id="btn2">
<h2>Button 2</h2>
</div>
<div class="button clickable" data-id="btn3">
<h2>Button 3</h2>
</div>
</div>
使用django.utils.RssFeed.write()
作为处理程序写入项目。此处理程序派生自django.utils.xmlutils.SimplerXMLGenerator
,它具有xml.sax.saxutils.XMLGenerator
- 方法,可以转义所有内容。因此,要将 unescape所有放入Feed,首先覆盖XML处理程序:
characters
下一步是覆盖Feed的write-method以使用新的处理程序。这里以Rss2.01 Feed为例:
from django.utils.xmlutils import SimplerXMLGenerator
class UnescapedXMLGenerator(SimplerXMLGenerator):
def characters(self, content):
"""
code is mainly copy-paste from Django 1.10 SimplerXMLGenerator.characters
"""
if content and re.search(r'[\x00-\x08\x0B-\x0C\x0E-\x1F]', content):
# Fail loudly when content has control chars (unsupported in XML 1.0)
# See http://www.w3.org/International/questions/qa-controls
raise UnserializableContentError("Control characters are not supported in XML 1.0")
# next part from sax.saxutils.XMLGenerator, but without escaping
if not isinstance(content, unicode):
content = unicode(content, self._encoding)
self._write(content)
答案 2 :(得分:0)
这是我如何将CDATA标记获取到输出中而又不将其转义的方法。我创建了AppleGenerator,它继承了Rss20rev2Feed默认使用的SimplerXMLGenerator。然后,我去掉了Rss201rev2feed用来使用我创建的新AppleGenerator的写入功能。然后,对于AppleGenerator,我覆盖了字符并使用addQuickElement函数接受可选输入以根据需要禁用转义。
from django.utils.xmlutils import SimplerXMLGenerator
from xml.sax.saxutils import escape
class AppleGenerator(SimplerXMLGenerator):
def addQuickElement(self, name, contents=None, attrs=None, escape_char=True):
"Convenience method for adding an element with no children"
if attrs is None: attrs = {}
self.startElement(name, attrs)
if contents is not None:
self.characters(contents, escape_char=escape_char)
self.endElement(name)
def characters(self, content, escape_char=True):
if content:
self._finish_pending_start_element()
if not isinstance(content, str):
content = str(content, self._encoding)
if escape_char:
self._write(escape(content))
else:
self._write(content)
class ApplePodcastsFeedGenerator(Rss201rev2Feed):
def write(self, outfile, encoding):
handler = AppleGenerator(outfile, encoding)
handler.startDocument()
handler.startElement("rss", self.rss_attributes())
handler.startElement("channel", self.root_attributes())
self.add_root_elements(handler)
self.write_items(handler)
self.endChannelElement(handler)
handler.endElement("rss")
覆盖基本上是该函数之前所做的事情,但是添加了一种不转义它们的方法。这是saxutils的源代码:
https://github.com/python/cpython/blob/3.7/Lib/xml/sax/saxutils.py
这是django SimplerXMLGenerator的源代码: https://github.com/django/django/blob/master/django/utils/xmlutils.py
答案 3 :(得分:0)
您可以替换代码:
contents = '<![CDATA[ contents ]]'
xml.addQuickElement('element', contents=contents)
具有:
contents = 'contents'
xml.startElement('element', {})
xml._write(f'<![CDATA[ {contents} ]]')
xml.endElement('element')
答案 4 :(得分:0)
这仍然是这个问题在谷歌上的第一次点击,所以这是基于尼克的回复 here 的完整答案:
python -m http.server 8000
以上与 Django 的处理程序相同,但添加了额外的“unescaped_characters”方法,并对“内容”进行条件检查以查看它是否以“
从这一点开始,您可以向您的 Feed 类添加一个新的“write()”方法,这应该按照 Django 代码的注释中的说明完成以覆盖处理程序方法,它提供您修改后的处理程序,就像这样,与原始但用于替换处理程序类定义:
from xml.sax.saxutils import XMLGenerator
class MySimplerXMLGenerator(XMLGenerator):
def addQuickElement(self, name, contents=None, attrs=None):
"Convenience method for adding an element with no children"
if attrs is None:
attrs = {}
self.startElement(name, attrs)
if contents is not None:
if contents.startswith('<![CDATA['):
self.unescaped_characters(contents)
else:
self.characters(contents)
self.endElement(name)
def characters(self, content):
if content and re.search(r'[\x00-\x08\x0B-\x0C\x0E-\x1F]', content):
# Fail loudly when content has control chars (unsupported in XML 1.0)
# See https://www.w3.org/International/questions/qa-controls
raise UnserializableContentError("Control characters are not supported in XML 1.0")
XMLGenerator.characters(self, content)
def unescaped_characters(self, content):
if content and re.search(r'[\x00-\x08\x0B-\x0C\x0E-\x1F]', content):
# Fail loudly when content has control chars (unsupported in XML 1.0)
# See https://www.w3.org/International/questions/qa-controls
raise UnserializableContentError("Control characters are not supported in XML 1.0")
XMLGenerator.ignorableWhitespace(self, content)
def startElement(self, name, attrs):
# Sort attrs for a deterministic output.
sorted_attrs = dict(sorted(attrs.items())) if attrs else attrs
super().startElement(name, sorted_attrs)
现在你有了一个新的处理程序,它可以有选择地不转义以 'bleach...
class iTunesPodcastsFeedGenerator(Rss201rev2Feed):
def write(self, outfile, encoding):
handler = MySimplerXMLGenerator(outfile, encoding)
handler.startDocument()
handler.startElement("rss", self.rss_attributes())
handler.startElement("channel", self.root_attributes())
self.add_root_elements(handler)
self.write_items(handler)
self.endChannelElement(handler)
handler.endElement("rss")
在撰写本文时,Apple(以及大部分其他播客目录)允许在描述中使用段落、无序列表和链接,因此以上是一个应该可以正常工作的播客提要示例。