Repository: kartoza/prj.app

Issue:
We need to improve the RSS feed
Gents,

I thought to give the sponsorlist atom feed a chance today...

http://changelog.qgis.org/en/qgis/sponsors/rss/

http://changelog.qgis.org/en/qgis/sponsors/atom/

Some things I think we have to tackle:

* Images

Should we pull all images from the changelog server
http://changelog.qgis.org

OR should we do some downloading of a zip with all stuff and let it
unzip by some cronjob?

An example:

http://changelog.qgis.org/media/images/projects/5f50b148aecade6339b100f1ad30897b81ca9a09.jpg

* Http vs HttpS

Now that the website is 100% https, all links to images etc should also
go to httpS...
Which makes pointing to a http://changelog.qgis.org only possible if we
create a reverseproxy for it on qgis2 ?

* Sorting in list(s)

Currently both atom and rss entry are not easily suited to be used in
some javascript:

ATOM entry:

 <entry>
   <title>QGIS user group Switzerland</title>
   <link
href="http://changelog.kartoza.com/en/qgis/sponsorshipperiod/dwlwhd/"
rel="alternate"/>
   <id>http://changelog.kartoza.com/en/qgis/sponsorshipperiod/dwlwhd/</id>
   <summary type="html">&lt;div&gt;&lt;img
src="/media/images/projects/945e5e1463ca738057fd9bae0a427519ccbfef0d.png"
width="300px"&gt;&lt;/div&gt;&lt;p&gt;&lt;span&gt;Sponsorship level:
Gold : 9000 EUR&lt;/span&gt;&lt;br/&gt;&lt;span&gt;Sponsorship period:
21 June 201>
 </entry>


RSS entry:

   <item>
     <title>QGIS user group Switzerland</title>

<link>http://changelog.kartoza.com/en/qgis/sponsorshipperiod/dwlwhd/</link>
     <description>&lt;div&gt;&lt;img
src="/media/images/projects/945e5e1463ca738057fd9bae0a427519ccbfef0d.png"
width="300px"&gt;&lt;/div&gt;&lt;p&gt;&lt;span&gt;Sponsorship level:
Gold : 9000 EUR&lt;/span&gt;&lt;br/&gt;&lt;span&gt;Sponsorship period:
21 June 2017 - 21>

<guid>http://changelog.kartoza.com/en/qgis/sponsorshipperiod/dwlwhd/</guid>
   </item>


To know if it is a gold or silver or bronze sponsor I would need to
regexp in the description. Which is doable by the way...

But to order the sponsors. I think they are ordered by end-time of
sponsorship I have to pull out the Sponsorship period and try to parse
both begin and endtime etc etc

Can we maybe come up with a javascript friendlier format?
FYI this
https://github.com/qgis/QGIS-Website/blob/master/themes/qgis-theme/languageswitch.html
Is the already becoming pretty complicated script to handle the feeds we
have now...

Happy to do skype call if easier.

Regards,

Richard Duivenvoorde


CC @rduivenvoorde 


Discussion:
Look at https://jsonfeed.org. It's more or less JSON feed based "à la RSS/Atom".
It supports extensions where you could deal with your sponsoring fields requirements https://jsonfeed.org/version/1#extensions

Resolving patch:
diff --git a/django_project/changes/feeds/json_rss_feed.py b/django_project/changes/feeds/json_rss_feed.py
new file mode 100644
--- /dev/null
+++ b/django_project/changes/feeds/json_rss_feed.py
@@ -0,0 +1,57 @@
+# coding=utf-8
+import json
+from django.utils.feedgenerator import SyndicationFeed, rfc2822_date
+
+
+class JSONFeed(SyndicationFeed):
+    mime_type = 'application/json; charset=utf-8'
+
+    def write(self, outfile, encoding):
+        data = {}
+        data['rss'] = {'version': '2.0'}
+        data['rss']['channel'] = self.add_root_elements()
+
+        if self.items:
+            item_element = []
+
+            for item in self.items:
+                item_element += [self.add_item_elements(item), ]
+
+            data['rss']['channel']['item'] = item_element
+
+        outfile.write(json.dumps(data, encoding=encoding))
+
+    def add_item_elements(self, item):
+        item_elements = {}
+
+        item_elements['title'] = item['title']
+        item_elements['link'] = item['link']
+        if item['description'] is not None:
+            item_elements['description'] = item['description']
+
+        if item['pubdate'] is not None:
+            item_elements['pubDate'] = rfc2822_date(item['pubdate'])
+        if item['unique_id'] is not None:
+            guid_attrs = {}
+            if isinstance(item.get('unique_id_is_permalink'), bool):
+                guid_attrs['isPermaLink'] = str(
+                    item['unique_id_is_permalink']).lower()
+            item_elements['guid'] = item['unique_id']
+
+        # Add image url attributes for sponsor
+        if item['image_url'] is not None:
+            domain_url = self.feed['link']
+            head, sep, tail = domain_url.partition('/en/')
+            item_elements['image_url'] = head + item['image_url']
+
+        return item_elements
+
+    def add_root_elements(self):
+        root_elements = {}
+
+        root_elements['title'] = self.feed['title']
+        root_elements['link'] = self.feed['link']
+        root_elements['description'] = self.feed['description']
+        root_elements['lastBuildDate'] = rfc2822_date(self.latest_post_date())
+
+        return root_elements
diff --git a/django_project/changes/feeds/sponsor.py b/django_project/changes/feeds/sponsor.py
--- a/django_project/changes/feeds/sponsor.py
+++ b/django_project/changes/feeds/sponsor.py
@@ -9,6 +9,7 @@
 from django.shortcuts import get_object_or_404
 from base.models.project import Project
 from changes.models.sponsorship_period import SponsorshipPeriod
+from changes.feeds.json_rss_feed import JSONFeed
 
 
 # noinspection PyMethodMayBeStatic
@@ -119,9 +120,19 @@ def item_description(self, item):
             .format(**data)
         return descriptions
 
+    def item_extra_kwargs(self, item):
+        return {'image_url': item.sponsor.logo.url}
+
+
 
 class AtomSponsorFeed(RssSponsorFeed):
     """Atom Feed class for sponsor."""
 
     feed_type = Atom1Feed
     subtitle = RssSponsorFeed.description
+
+
+class JSONSponsorFeed(RssSponsorFeed):
+    """JSON Feed class for sponsor."""
+
+    feed_type = JSONFeed
diff --git a/django_project/changes/urls.py b/django_project/changes/urls.py
--- a/django_project/changes/urls.py
+++ b/django_project/changes/urls.py
@@ -8,7 +8,7 @@
 
 from feeds.version import RssVersionFeed, AtomVersionFeed
 from feeds.entry import RssEntryFeed, AtomEntryFeed
-from feeds.sponsor import RssSponsorFeed, AtomSponsorFeed
+from feeds.sponsor import RssSponsorFeed, AtomSponsorFeed, JSONSponsorFeed
 from views import (
     # Category
     CategoryDetailView,
@@ -225,6 +225,9 @@
     url(regex='^(?P<project_slug>[\w-]+)/sponsors/atom/$',
         view=AtomSponsorFeed(),
         name='sponsor-atom-feed'),
+    url(regex='^(?P<project_slug>[\w-]+)/sponsors/json/$',
+        view=JSONSponsorFeed(),
+        name='sponsor-json-feed'),
 
     # User map
     # url(r'^user-map/', include('user_map.urls')),


Test patch:
diff --git a/django_project/changes/tests/test_json_rss_feed.py b/django_project/changes/tests/test_json_rss_feed.py
new file mode 100644
--- /dev/null
+++ b/django_project/changes/tests/test_json_rss_feed.py
@@ -0,0 +1,79 @@
+# coding=utf-8
+from django.core.urlresolvers import reverse
+from django.test import TestCase, override_settings
+from django.test.client import Client
+from base.tests.model_factories import ProjectF
+from changes.tests.model_factories import (
+    SponsorshipLevelF,
+    SponsorF,
+    SponsorshipPeriodF)
+from core.model_factories import UserF
+import logging
+
+
+class JSONFeedTest(TestCase):
+    """JSON Feed unittest."""
+
+    @override_settings(VALID_DOMAIN=['testserver', ])
+    def setUp(self):
+        """
+        Setup before each test
+
+        We force the locale to en otherwise it will use
+        the locale of the host running the tests and we
+        will get unpredictable results / 404s
+        """
+
+        self.client = Client()
+        self.client.post(
+            '/set_language/', data={'language': 'en'})
+        logging.disable(logging.CRITICAL)
+        self.user = UserF.create(**{
+            'username': 'anita',
+            'password': 'password',
+            'is_staff': True
+        })
+        self.user.set_password('password')
+        self.user.save()
+        self.project = ProjectF.create(
+            name='testproject')
+        self.sponsor = SponsorF.create(
+            project=self.project,
+            name='Kartoza')
+        self.sponsorship_level = SponsorshipLevelF.create(
+            project=self.project,
+            name='Gold')
+        self.sponsorship_period = SponsorshipPeriodF.create(
+            sponsor=self.sponsor,
+            sponsorship_level=self.sponsorship_level,
+            project=self.project,
+            approved=True)
+
+    @override_settings(VALID_DOMAIN=['testserver', ])
+    def tearDown(self):
+        """
+        Teardown after each test.
+
+        :return:
+        """
+        self.project.delete()
+        self.sponsor.delete()
+        self.sponsorship_level.delete()
+        self.sponsorship_period.delete()
+        self.user.delete()
+
+    @override_settings(VALID_DOMAIN=['testserver', ])
+    def test_JSON_feed_view(self):
+        response = self.client.get(reverse('sponsor-json-feed', kwargs={
+            'project_slug': self.project.slug
+        }))
+        self.assertEqual(response.status_code, 200)
+
+    @override_settings(VALID_DOMAIN=['testserver', ])
+    def test_is_JSON_feed(self):
+        response = self.client.get(reverse('sponsor-json-feed', kwargs={
+            'project_slug': self.project.slug
+        }))
+        self.assertEqual(
+            'application/json; charset=utf-8',
+            response._headers['content-type'][1])