Introduce RSSFeedWidget

This commit is contained in:
jeremystretch 2023-03-14 10:16:18 -04:00
parent 28f678ca06
commit b71adcad49
3 changed files with 78 additions and 2 deletions

View File

@ -1,7 +1,11 @@
import uuid
from functools import cached_property
from hashlib import sha256
import feedparser
from django import forms
from django.contrib.contenttypes.models import ContentType
from django.core.cache import cache
from django.template.loader import render_to_string
from django.utils.translation import gettext as _
@ -15,6 +19,7 @@ __all__ = (
'NoteWidget',
'ObjectCountsWidget',
'ObjectListWidget',
'RSSFeedWidget',
)
@ -27,6 +32,7 @@ def get_content_type_labels():
class DashboardWidget:
default_title = None
default_config = {}
description = None
width = 4
height = 3
@ -36,7 +42,7 @@ class DashboardWidget:
def __init__(self, id=None, title=None, color=None, config=None, width=None, height=None, x=None, y=None):
self.id = id or str(uuid.uuid4())
self.config = config or {}
self.config = config or self.default_config
self.title = title or self.default_title
self.color = color
if width:
@ -72,6 +78,7 @@ class DashboardWidget:
@register_widget
class NoteWidget(DashboardWidget):
default_title = _('Note')
description = _('Display some arbitrary custom content. Markdown is supported.')
class ConfigForm(BootstrapMixin, forms.Form):
@ -128,3 +135,59 @@ class ObjectListWidget(DashboardWidget):
return render_to_string(self.template_name, {
'viewname': viewname,
})
@register_widget
class RSSFeedWidget(DashboardWidget):
default_title = _('RSS Feed')
default_config = {
'max_entries': 10,
'cache_timeout': 3600, # seconds
}
description = _('Embed an RSS feed from an external website.')
template_name = 'extras/dashboard/widgets/rssfeed.html'
width = 6
height = 4
class ConfigForm(BootstrapMixin, forms.Form):
feed_url = forms.URLField(
label=_('Feed URL')
)
max_entries = forms.IntegerField(
min_value=1,
max_value=1000,
help_text=_('The maximum number of objects to display')
)
cache_timeout = forms.IntegerField(
min_value=600, # 10 minutes
max_value=86400, # 24 hours
help_text=_('How long to stored the cached content (in seconds)')
)
def render(self, request):
url = self.config['feed_url']
feed = self.get_feed()
return render_to_string(self.template_name, {
'url': url,
'feed': feed,
})
@cached_property
def cache_key(self):
url = self.config['feed_url']
url_checksum = sha256(url.encode('utf-8')).hexdigest()
return f'dashboard_rss_{url_checksum}'
def get_feed(self):
# Fetch RSS content from cache
if feed_content := cache.get(self.cache_key):
feed = feedparser.FeedParserDict(feed_content)
else:
feed = feedparser.parse(self.config['feed_url'])
# Cap number of entries
max_entries = self.config.get('max_entries')
feed['entries'] = feed['entries'][:max_entries]
cache.set(self.cache_key, dict(feed), self.config.get('cache_timeout'))
return feed

View File

@ -686,7 +686,7 @@ class DashboardWidgetAddView(LoginRequiredMixin, View):
widget_form = DashboardWidgetAddForm(initial=initial)
widget_name = get_field_value(widget_form, 'widget_class')
widget_class = get_widget_class(widget_name)
config_form = widget_class.ConfigForm(prefix='config')
config_form = widget_class.ConfigForm(initial=widget_class.default_config, prefix='config')
return render(request, self.template_name, {
'widget_class': widget_class,

View File

@ -0,0 +1,13 @@
<ul class="list-group list-group-flush">
{% for entry in feed.entries %}
<li class="list-group-item px-1">
<h6><a href="{{ entry.link }}">{{ entry.title }}</a></h6>
<div>
{{ entry.summary|safe }}
</div>
</li>
{% empty %}
<li class="list-group-item">No entries</li>
{% endfor %}
</ul>