<?xml version="1.0" encoding="utf-8" standalone="yes"?><?xml-stylesheet href="/rss.xsl" type="text/xsl"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/"><title>Rss on Hyteck</title><link href="https://hyteck.de/tags/rss/"/><generator>Hugo -- gohugo.io</generator><language>en-us</language><id>https://hyteck.de/tags/rss/</id><updated>2024-12-11T06:10:10+02:00</updated><link href="https://hyteck.de/tags/rss/index.xml" rel="self" type="application/rss+xml"/><entry><title>I did something naughty: Circumventing Authorized-Fetch as implemented by GoToSocial</title><link href="https://hyteck.de/post/public-posts-with-authorized-fetch/" type="application/octet-stream"/><updated>2024-12-11T06:10:10+02:00</updated><id>https://hyteck.de/post/public-posts-with-authorized-fetch/</id><author><name>Julian-Samuel Gebühr</name></author><content type="html"> &lt;p>Yes the title is correct, but I had nothing malicious in mind!&lt;/p>
&lt;h2 id="what-this-is-about">What this is about&lt;/h2>
&lt;p>For &lt;a href="https://social.queereszentrumtuebingen.de/@qzt">@qzt@queereszentrumtuebingen.de&lt;/a> we include the public feed &lt;a href="https://queereszentrumtuebingen.de/">in a sidbar on the homepage&lt;/a>. Initially this was done using the standard API to fetch statuses &lt;code>/api/v1/accounts/{account_id}/statuses&lt;/code> and worked like a charm. The problem started when &lt;a href="https://gotosocial.org/">GoToSocial&lt;/a> (the fediverse server we use, similar to mastodon) implemented authorized fetch. This is a a good thing! Authorized fetch means, that every call to a endpoint needs to be authorized by an &lt;code>access_token&lt;/code>. You get an access token from a fedi account. It&amp;rsquo;s what fediverse clients like Tusky or Phanpy do on your behalf to get the posts that make up you timeline.&lt;/p>
&lt;p>Authorized fetch has major advantages as&lt;/p>
&lt;ul>
&lt;li>data scraping can only be done by other fediaccounts&lt;/li>
&lt;li>blocking can not be circumvented by using the public API&lt;/li>
&lt;/ul>
&lt;p>and much more. Sadly it also broke our website integration.&lt;/p>
&lt;h2 id="possible-solutions">Possible Solutions&lt;/h2>
&lt;p>So what now? I initially wanted to turn of authorized fetch for &lt;a href="https://social.queereszentrumtuebingen.de/@qzt">@qzt@queereszentrumtuebingen.de&lt;/a> by messing with the GoToSocial code and turning it off for the whole server. This would have been possible as this is the only user on the server. The GoToSocial devs helped me manage to find where to do that. But it&amp;rsquo;s not ideal and would make me build a custom docker image fore each update.&lt;/p>
&lt;p>Next idea: The whole point of authorized fetch is, that only fedi-accounts (and apps they authorized) can access the API. So lets do that! Set up a new account, add app and authorize it &lt;a href="https://docs.gotosocial.org/en/latest/api/authentication/">as described in the GoToSocial documentation&lt;/a>. I used #Bruno for that, that was much more comfortable than using curl for me.
With that authorization code you can now get an access token for your app. Put that in the Javascript that loads posts and we are good right? Sadly no. It would totally work. But it would also allow anyone to read and post on behalf of the account. That calls for malicious actors using this for scraping or spamming.&lt;/p>
&lt;p>So instead, we need a proxy that stores the access token securely and restricts the actions.&lt;/p>
&lt;h2 id="the-proxy">The proxy&lt;/h2>
&lt;p>Such a proxie must&lt;/p>
&lt;ul>
&lt;li>offer the endpoint that provides the same data as the FediverseAPI&lt;/li>
&lt;li>authorize itself to the FediverseAPI via &lt;code>access_token&lt;/code>&lt;/li>
&lt;li>restrict to read access of consenting accounts&lt;/li>
&lt;/ul>
&lt;p>The last point is really important, as we don&amp;rsquo;t want to allow others to use this endpoint to scrape data unauthorized.&lt;/p>
&lt;p>I wrote a short FastAPI server that offers this. It only implements one method&lt;/p>
&lt;pre tabindex="0">&lt;code>@app.get(&amp;#34;/api/v1/accounts/{account_id}/statuses&amp;#34;)
async def fetch_data(account_id):
if account_id not in ALLOWED_ACCOUNTS:
raise HTTPException(status_code=401, detail=&amp;#34;You can only use this proxy to access configured accounts&amp;#34;)
headers = {&amp;#34;Authorization&amp;#34;: f&amp;#34;Bearer {ACCESS_TOKEN}&amp;#34;}
response = requests.get(f&amp;#34;{EXTERNAL_API_BASE_URL}/api/v1/accounts/{account_id}/statuses&amp;#34;, headers=headers)
return response.json()
&lt;/code>&lt;/pre>&lt;p>Basically this is the whole API code, I only trimmed a few checks and error handling.&lt;/p>
&lt;h2 id="deployment">Deployment&lt;/h2>
&lt;p>To deploy, I put it in a docker container and started it via docker-compose. Reverse proxing is handled by Traefik, I won&amp;rsquo;t go into detail here.&lt;/p>
&lt;pre tabindex="0">&lt;code>services:
fediproxy.example.org:
image: docker.io/moanos/fediproxy
container_name: &amp;#34;fediproxy.example.org&amp;#34;
restart: unless-stopped
environment:
EXTERNAL_API_BASE_URL: ${EXTERNAL_API_BASE_URL}
ACCESS_TOKEN: ${ACCESS_TOKEN}
ALLOWED_ACCOUNTS: ${ALLOWED_ACCOUNTS}
labels:
- &amp;#34;traefik.enable=true&amp;#34;
- &amp;#34;traefik.docker.network=traefik&amp;#34;
- &amp;#34;traefik.http.routers.fediproxy.rule=Host(`fediproxy.example.org`)&amp;#34;
- &amp;#34;traefik.http.routers.fediproxy.service=fediproxy-service&amp;#34;
- &amp;#34;traefik.http.routers.fediproxy.entrypoints=web-secure&amp;#34;
- &amp;#34;traefik.http.routers.fediproxy.tls=true&amp;#34;
- &amp;#34;traefik.http.routers.fediproxy.tls.certResolver=default&amp;#34;
- &amp;#34;traefik.http.services.fediproxy-service.loadbalancer.server.port=8000&amp;#34;
networks:
- traefik
networks:
traefik:
name: &amp;#34;traefik&amp;#34;
external: true
&lt;/code>&lt;/pre>&lt;p>I added a short &lt;code>.env&lt;/code> to configure:&lt;/p>
&lt;pre tabindex="0">&lt;code>ACCESS_TOKEN=VERYSECRETTOKENTHATISDEFINETLYREAL
EXTERNAL_API_BASE_URL=https://gay-pirate-assassins.de
ALLOWED_ACCOUNTS=ZGGZF4G8NNOTREAL81Z8G7RTC
&lt;/code>&lt;/pre>&lt;h2 id="results">Results&lt;/h2>
&lt;p>Now I can again use something like &lt;a href="https://wordpress.org/plugins/include-mastodon-feed/#installation">the wordpress plugin Include Mastodon Feed&lt;/a> just by pointing to the proxy: &lt;code>[include-mastodon-feed instance=&amp;quot;fediproxy.example.org.de&amp;quot; account=&amp;quot;ZGGZF4G8NNOTREAL81Z8G7RTC&amp;quot;]&lt;/code>&lt;/p>
&lt;p>Hope you enjoyed the read. Source code for the proxy can be found here: &lt;a href="https://git.hyteck.de/moanos/FediProxy">https://git.hyteck.de/moanos/FediProxy&lt;/a>
If you want to play around a bit you can use &lt;a href="https://git.hyteck.de/moanos/include-fedi">https://git.hyteck.de/moanos/include-fedi&lt;/a>&lt;/p>
&lt;p>Sloth logo of GTS by &lt;a href="https://abramek.art/">Anna Abramek&lt;/a>, &lt;a href="http://creativecommons.org/licenses/by-sa/4.0/">Creative Commons BY-SA license&lt;/a>.&lt;/p></content></entry><entry><title>Styling an Django RSS Feed</title><link href="https://hyteck.de/post/django-rss/" type="application/octet-stream"/><updated>2024-04-16T12:10:10+02:00</updated><id>https://hyteck.de/post/django-rss/</id><author><name>Julian-Samuel Gebühr</name></author><content type="html"> &lt;h2 id="introduction">Introduction&lt;/h2>
&lt;p>RSS is amazing! While not everyone thinks that, most people that &lt;em>understand&lt;/em> RSS, like it. This presents a problem, as most people don&amp;rsquo;t have chance to learn about it. Unless there is a person in the community that doesn&amp;rsquo;t shut up about how great RSS is (maybe that person is you), they might not even know what it is, let alone use it.&lt;/p>
&lt;p>One big reason for this is, that when you click an link to an RSS feed you download a strange file that most people don&amp;rsquo;t know how to deal with. Maybe your browser is nice and renders some XML which is also not meant for human consumption. Wouldn&amp;rsquo;t it be better if people clicked on the RSS link and were greeted by a text explaining RSS and how to use it? And if the site would still be a valid RSS feed?&lt;/p>
&lt;p>Luckily you don&amp;rsquo;t have to imagine that - it&amp;rsquo;s possible! You can even try it on this blog by clicking the RSS link in the menu (&lt;a href="https://hyteck.de/index.xml">direct link&lt;/a>).&lt;/p>
&lt;p>Doing this has not been my idea. Darek Kay described this in the blog post &lt;a href="https://darekkay.com/blog/rss-styling/">Style your RSS feed&lt;/a> and I just copied most of their work! This was fairly easy for this Hugo blog and is &lt;a href="https://github.com/moan0s/hugo-nederburg-theme">available in my fork of the hugo-nederburg-theme&lt;/a>. However, in a Django project it get&amp;rsquo;s a bit more complicated. Let me explain.&lt;/p>
&lt;h2 id="the-problem">The Problem&lt;/h2>
&lt;p>Django has the great &lt;a href="https://docs.djangoproject.com/en/5.0/ref/contrib/syndication/">Syndication feed framework&lt;/a>, a high level framework to create RSS and Atom Feeds. This is great as we only need a few lines of code to create a feed. Here is an example from &lt;a href="https://notfellchen.org">notfellchen.org&lt;/a> that list animals that are in search for a new home. People should be able to follow the RSS feed to see new adoption notices. So lets do it&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># in src/fellchennasen/feeds.py&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> django.contrib.syndication.views &lt;span style="color:#f92672">import&lt;/span> Feed
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> .models &lt;span style="color:#f92672">import&lt;/span> AdoptionNotice
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">LatestAdoptionNoticesFeed&lt;/span>(Feed):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> title &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Notfellchen&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> link &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;/rss/&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> description &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Updates zu neuen Vermittlungen.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">items&lt;/span>(self):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> AdoptionNotice&lt;span style="color:#f92672">.&lt;/span>objects&lt;span style="color:#f92672">.&lt;/span>order_by(&lt;span style="color:#e6db74">&amp;#34;-created_at&amp;#34;&lt;/span>)[:&lt;span style="color:#ae81ff">5&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">item_title&lt;/span>(self, item):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> item&lt;span style="color:#f92672">.&lt;/span>name
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">item_description&lt;/span>(self, item):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> item&lt;span style="color:#f92672">.&lt;/span>description
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># in src/fellchennasen/urls.py&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>urlpatterns &lt;span style="color:#f92672">=&lt;/span> [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> path(&lt;span style="color:#e6db74">&amp;#34;&amp;#34;&lt;/span>, views&lt;span style="color:#f92672">.&lt;/span>index, name&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;index&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> path(&lt;span style="color:#e6db74">&amp;#34;rss/&amp;#34;&lt;/span>, LatestAdoptionNoticesFeed(), name&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;rss&amp;#34;&lt;/span>), &lt;span style="color:#75715e"># &amp;lt;--- Added&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">...&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Wait that&amp;rsquo;s it? Yeah! We have a working RSS feed. And it was very convenient, Django allows us to create by just pointing it to the right model and fields we want to display.&lt;/p>
&lt;p>But here is the problem: How do we style this? We can&amp;rsquo;t just add a link to a stylesheet here.&lt;/p>
&lt;h2 id="the-solution">The solution&lt;/h2>
&lt;p>First we need to add our styling files. I&amp;rsquo;ll not go into detail how they work her, just refer &lt;a href="https://darekkay.com/blog/rss-styling/">Darek&amp;rsquo;s blog post&lt;/a> for that. In Django we add them to our static files&lt;/p>
&lt;ul>
&lt;li>&lt;code>static/rss.xsl&lt;/code> will be adjusted based on &lt;a href="rss.xsl">this file&lt;/a>. It is responsible for creating a html rendering of your XML file&lt;/li>
&lt;li>for &lt;code>static/css/rss-styles&lt;/code> you can drop in &lt;a href="rss-styles.css">this file&lt;/a>, which is a basic CSS file you can edit to your liking.&lt;/li>
&lt;/ul>
&lt;p>After that comes the hard part. How do tweak this wonderfully simple Feed class to include a link to our style sheet? I first thought &amp;ldquo;that must be easy, just follow the docs on &lt;a href="https://docs.djangoproject.com/en/5.0/ref/contrib/syndication/#custom-feed-generators">custom feed generators&lt;/a> and add a root element. Something like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">FormattedFeed&lt;/span>(Rss201rev2Feed):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">add_root_elements&lt;/span>(self, handler):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> super()&lt;span style="color:#f92672">.&lt;/span>add_root_elements(handler)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># We want &amp;lt;?xml-stylesheet href=&amp;#34;/static/rss.xsl&amp;#34; type=&amp;#34;text/xsl&amp;#34;?&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> handler&lt;span style="color:#f92672">.&lt;/span>addQuickElement(&lt;span style="color:#e6db74">&amp;#34;?xml-stylesheet&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#39;href=&amp;#34;&lt;/span>&lt;span style="color:#e6db74">{&lt;/span>static(&lt;span style="color:#e6db74">&amp;#34;rss.xsl&amp;#34;&lt;/span>)&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">LatestAdoptionNoticesFeed&lt;/span>(Feed):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> feed_type &lt;span style="color:#f92672">=&lt;/span> FormattedFeed
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> title &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Notfellchen&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">...&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Looks good. Let&amp;rsquo;s try. Oh no what is this?&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-xml" data-lang="xml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&amp;lt;&lt;/span>?xml-stylesheet href=&amp;#34;/static/rss.xsl&amp;#34;/&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Yes, we can&amp;rsquo;t correctly close this tag. There is (to my knowledge) no easy way to do this. So let&amp;rsquo;s take the hard road an implement a custom write function. In the following the write function will be copied from &lt;code>django.utils.feedgenerator.RssFeed&lt;/code>. We make two important changes to the class:&lt;/p>
&lt;ol>
&lt;li>Changing the content type from &lt;code>content_type = &amp;quot;application/rss+xml; charset=utf-8&amp;quot;&lt;/code> to &lt;code>content_type = &amp;quot;text/rss+xml; charset=utf-8&lt;/code>. This will make a browser display the content rather than opening it in a app.&lt;/li>
&lt;li>Adding our xml-stylsheet information. This is done in the &lt;code>write()&lt;/code> function with this line&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>handler&lt;span style="color:#f92672">.&lt;/span>_write(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&amp;lt;?xml-stylesheet href=&amp;#34;&lt;/span>&lt;span style="color:#e6db74">{&lt;/span>static(&lt;span style="color:#e6db74">&amp;#34;rss.xsl&amp;#34;&lt;/span>)&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34; type=&amp;#34;text/xsl&amp;#34;?&amp;gt;&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Putting it all together we still have a relativly simple solution with only the necessary adjustments. Here is the full &lt;code>feeds.py&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> django.contrib.syndication.views &lt;span style="color:#f92672">import&lt;/span> Feed
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> django.utils.feedgenerator &lt;span style="color:#f92672">import&lt;/span> Rss201rev2Feed
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> django.templatetags.static &lt;span style="color:#f92672">import&lt;/span> static
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> django.utils.xmlutils &lt;span style="color:#f92672">import&lt;/span> SimplerXMLGenerator
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> .models &lt;span style="color:#f92672">import&lt;/span> AdoptionNotice
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">FormattedFeed&lt;/span>(Rss201rev2Feed):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> content_type &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;text/xml; charset=utf-8&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">write&lt;/span>(self, outfile, encoding):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> handler &lt;span style="color:#f92672">=&lt;/span> SimplerXMLGenerator(outfile, encoding, short_empty_elements&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">True&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> handler&lt;span style="color:#f92672">.&lt;/span>startDocument()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> handler&lt;span style="color:#f92672">.&lt;/span>_write(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&amp;lt;?xml-stylesheet href=&amp;#34;&lt;/span>&lt;span style="color:#e6db74">{&lt;/span>static(&lt;span style="color:#e6db74">&amp;#34;rss.xsl&amp;#34;&lt;/span>)&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34; type=&amp;#34;text/xsl&amp;#34;?&amp;gt;&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> handler&lt;span style="color:#f92672">.&lt;/span>startElement(&lt;span style="color:#e6db74">&amp;#34;rss&amp;#34;&lt;/span>, self&lt;span style="color:#f92672">.&lt;/span>rss_attributes())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> handler&lt;span style="color:#f92672">.&lt;/span>startElement(&lt;span style="color:#e6db74">&amp;#34;channel&amp;#34;&lt;/span>, self&lt;span style="color:#f92672">.&lt;/span>root_attributes())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>add_root_elements(handler)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>write_items(handler)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>endChannelElement(handler)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> handler&lt;span style="color:#f92672">.&lt;/span>endElement(&lt;span style="color:#e6db74">&amp;#34;rss&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">LatestAdoptionNoticesFeed&lt;/span>(Feed):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> feed_type &lt;span style="color:#f92672">=&lt;/span> FormattedFeed
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> title &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Notfellchen&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> link &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;/rss/&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> description &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Updates zu neuen Vermittlungen.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">items&lt;/span>(self):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> AdoptionNotice&lt;span style="color:#f92672">.&lt;/span>objects&lt;span style="color:#f92672">.&lt;/span>order_by(&lt;span style="color:#e6db74">&amp;#34;-created_at&amp;#34;&lt;/span>)[:&lt;span style="color:#ae81ff">5&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">item_title&lt;/span>(self, item):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> item&lt;span style="color:#f92672">.&lt;/span>name
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">item_description&lt;/span>(self, item):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> item&lt;span style="color:#f92672">.&lt;/span>description
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>And finally we have what we want! A RSS feed displayed in the browser, with beginner-friendly explanation and still completely spec-compliant.&lt;/p>
&lt;p>&lt;img src="screenshot1.jpeg" alt="Screenshot of a website">&lt;/p>
&lt;h2 id="outlook">Outlook&lt;/h2>
&lt;p>Now you may recognize I&amp;rsquo;m not a frontend person. The style could be prettier and provide a better overview. But I&amp;rsquo;d argue the improvement is immense and might help a user to get started with RSS.&lt;/p>
&lt;p>There are still a couple things to improve:&lt;/p>
&lt;ul>
&lt;li>Translation: The current text is only displayed in english&lt;/li>
&lt;li>The &lt;code>rss.xsl&lt;/code> file has a hard-coded link to the css stylesheet in it&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-html" data-lang="html">&lt;span style="display:flex;">&lt;span>&amp;lt;&lt;span style="color:#f92672">link&lt;/span> &lt;span style="color:#a6e22e">rel&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;stylesheet&amp;#34;&lt;/span> &lt;span style="color:#a6e22e">type&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;text/css&amp;#34;&lt;/span> &lt;span style="color:#a6e22e">href&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;/static/fellchensammlung/css/rss-styles.css&amp;#34;&lt;/span>/&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Both can be solved by templating the &lt;code>rss.xsl&lt;/code> instead of serving it as static file.&lt;/p>
&lt;p>So have fun playing around! If you have created or found a nice-looking RSS feed let me know. Let&amp;rsquo;s keep RSS alive and thriving!&lt;/p>
&lt;script type="text/javascript" src="https://latest.cactus.chat/cactus.js">&lt;/script>
&lt;link rel="stylesheet" href="https://latest.cactus.chat/style.css" type="text/css">
&lt;div id="comment-section">&lt;/div>
&lt;script>
initComments({
node: document.getElementById("comment-section"),
defaultHomeserverUrl: "https://matrix.cactus.chat:8448",
serverName: "cactus.chat",
siteName: "hyteck",
commentSectionId: "django-rss"
})
&lt;/script></content></entry></feed>