We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
RSS feeds give a 504
For example: 
- http://rsr.akvo.org/rss/org-updates/273/
- http://rsr.akvo.org/rss/updates/788/


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `akvo/rsr/feeds.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 
3 # Akvo RSR is covered by the GNU Affero General Public License.
4 # See more details in the license.txt file located at the root folder of the Akvo RSR module. 
5 # For additional details on the GNU license please see < http://www.gnu.org/licenses/agpl.html >.
6 
7 import re
8 
9 from xml.sax.saxutils import XMLGenerator
10 
11 from django.contrib.syndication.views import FeedDoesNotExist, Feed
12 from django.core.urlresolvers import reverse
13 from django.shortcuts import get_object_or_404
14 from django.utils.feedgenerator import Rss201rev2Feed
15 from django.utils.translation import ugettext_lazy as _
16 
17 from akvo.rsr.models import Project, ProjectUpdate, Organisation
18 
19 
20 def __dict_replace(s, d):
21     """Replace substrings of a string using a dictionary."""
22     for key, value in d.items():
23         s = s.replace(key, value)
24     return s
25 
26 def __escape(data, entities):
27     # must do ampersand first
28     data = data.replace("&", "&amp;")
29     data  = data.replace(">", "&gt;")
30     data  = data.replace("<", "&lt;")
31     if entities:
32         data = __dict_replace(data, entities)
33     return data
34 
35 def escape(data, entities={}):
36     """Modification to xml.sax.saxutils.escape to that detects CDATA blocks that are not escaped
37 
38     Escape &, <, and > in a string of data.
39 
40     You can escape other strings of data by passing a dictionary as
41     the optional entities parameter.  The keys and values must all be
42     strings; each key will be replaced with its corresponding value.
43 
44     """
45     # find character data, re.DOTALL includes linefeed in .
46     pattern = re.compile('<!\[CDATA\[.*\]\]>', re.DOTALL)
47     iterator = pattern.finditer(data)
48     start = 0
49     bits = []
50     for match in iterator:
51         #grab chunk before first match
52         bit = data[start:match.span()[0]]
53         bit = __escape(bit, entities)
54         bits.append(bit)
55         #grab match
56         bit = data[match.span()[0]:match.span()[1]]
57         bits.extend(bit)
58         start = match.span()[1]
59     # escape tail bit after last match
60     bit = data[start:]
61     bit = __escape(bit, entities)
62     bits.extend(bit)
63     data = ''.join(bits)
64     return data
65 
66 
67 class RSRSimplerXMLGenerator(XMLGenerator):
68     """subclassed to be able to call custom escape() function, see above
69     """
70     def characters(self, content):
71         self._write(escape(content))
72 
73     def addQuickElement(self, name, contents=None, attrs=None):
74         "Convenience method for adding an element with no children"
75         if attrs is None: attrs = {}
76         self.startElement(name, attrs)
77         if contents is not None:
78             self.characters(contents)
79         self.endElement(name)
80 
81 
82 class RSRMediaRssFeed(Rss201rev2Feed):
83     def rss_attributes(self):
84         attrs = super(RSRMediaRssFeed, self).rss_attributes()
85         attrs['xmlns:media']    = 'http://search.yahoo.com/mrss/'
86         attrs['xmlns:atom']     = 'http://www.w3.org/2005/Atom'
87         return attrs
88 
89     def add_item_elements(self, handler, item):
90         """Callback to add elements to each item (item/entry) element."""
91         super(RSRMediaRssFeed, self).add_item_elements(handler, item)
92 
93         if 'media:title' in item:
94             handler.addQuickElement(u"media:title", item['title'])
95         if 'media:description' in item:
96             handler.addQuickElement(u"media:description", item['media:description'])
97         if 'media:credit' in item:
98             handler.addQuickElement(u"media:credit", item['media:credit'])
99 
100         if 'content_url' in item:
101             content = dict(url=item['content_url'])
102             if 'content_width' in item:
103                 content['width'] = str(item['content_width'])
104             if 'content_height' in item:
105                 content['height'] = str(item['content_height'])
106             handler.addQuickElement(u"media:content", '', content)
107 
108         if 'thumbnail_url' in item:
109             thumbnail = dict(url=item['thumbnail_url'])
110             if 'thumbnail_width' in item:
111                 thumbnail['width'] = str(item['thumbnail_width'])
112             if 'thumbnail_height' in item:
113                 thumbnail['height'] = str(item['thumbnail_height'])
114             handler.addQuickElement(u"media:thumbnail", '', thumbnail)
115 
116         if 'keywords' in item:
117             handler.addQuickElement(u"media:keywords", item['keywords'])
118 
119     def write(self, outfile, encoding):
120         handler = RSRSimplerXMLGenerator(outfile, encoding)
121         handler.startDocument()
122         handler.startElement(u"rss", self.rss_attributes())
123         handler.startElement(u"channel", self.root_attributes())
124         self.add_root_elements(handler)
125         self.write_items(handler)
126         self.endChannelElement(handler)
127         handler.endElement(u"rss")
128 
129 class UpdateFeed(Feed):
130     """base class generating Update feeds
131     """
132     feed_type = RSRMediaRssFeed
133 
134     def link(self, obj):
135         if not obj:
136             raise FeedDoesNotExist
137         return obj.get_absolute_url()
138 
139     def item_link(self, item):
140         return item.get_absolute_url()
141 
142     def item_title(self, item):
143         return item.title
144 
145     def item_description(self, item):
146         try:
147             size = item.photo.size
148             return '<![CDATA[<p><a href="%s"><img src="%s" alt="" /></a></p><p>%s</p>]]>' % (
149                 item.get_absolute_url(),
150                 item.photo.thumbnail.absolute_url,
151                 item.text,
152             )
153         except:
154             return item.text
155 
156     def item_pubdate(self, item):
157         return item.created_at
158 
159     def item_author_name(self, item):
160         return item.user.get_full_name()
161 
162     def item_credit(self, item):
163         return item.photo_credit
164 
165     def item_extra_kwargs(self, item):
166         """return a dictionary to the feedgenerator for each item to be added to the feed.
167         """
168         try:
169             size = item.photo.size
170             photo = item.photo
171             kwargs = {
172                 'media:title': item.title,
173                 'media:description': item.photo_caption,
174                 'media:credit': item.photo_credit,
175                 'content_url': photo.url,
176                 'content_width': photo.width,
177                 'content_height': photo.height,
178                 'thumbnail_url': photo.thumbnail.absolute_url,
179                 'thumbnail_width': photo.thumbnail.width(),
180                 'thumbnail_height': photo.thumbnail.height(),
181             }
182             return kwargs
183         except:
184             return {}
185 
186 
187 class ProjectUpdates(UpdateFeed):
188     """RSS feed for last 50 RSR updates of a project."""
189     def get_object(self, request, project_id):
190         return Project.objects.get(pk__exact=project_id)
191 
192     def title(self, obj):
193         return _(u'Akvo RSR project %(id)d: %(project_title)s') % {
194             'id': obj.id,
195             'project_title': obj.title
196         }
197 
198     def description(self, obj):
199         return _(u'Project updates for project %(project_title)s') % {
200             'project_title': obj.title
201         }
202 
203     def items(self, obj):
204         # Limited to 50 items to prevent gateway timeouts.
205         return ProjectUpdate.objects.filter(project__id=obj.id).order_by('-id')[:50]
206 
207 
208 class OrganisationUpdates(UpdateFeed):
209     """RSS feed for last 50 RSR updates of an organisation."""
210     feed_type = RSRMediaRssFeed
211 
212     def get_object(self, request, org_id):
213         return get_object_or_404(Organisation, id=int(org_id))
214 
215     def title(self, obj):
216         return _(u'Projects of %(org_name)s') % {'org_name':obj.name,}
217 
218     def description(self, obj):
219         if obj.name == obj.long_name:
220             return _(u"Project updates for projects partnered by %(org_name)s") % {
221                 'org_name': obj.name
222             }
223         else:
224             return _(
225                 u"Project updates for projects partnered by %(org_name)s - %(long_name)s"
226             ) % {'org_name': obj.name, 'long_name': obj.long_name}
227 
228     def items(self, obj):
229         # Limited to 50 items to prevent gateway timeouts.
230         return obj.published_projects().all_updates()[:50]
231 
232     def item_title(self, item):
233         return _(
234             u'Project %(project_id)d - %(project_title)s: %(update_title)s'
235         ) % {
236             'project_id': item.project.id,
237             'project_title': item.project.title,
238             'update_title': item.title
239         }
240 
241 
242 class AllProjectUpdates(UpdateFeed):
243     """RSS feed for last 50 RSR updates."""
244     title = _(u'Last 50 RSR project updates')
245     
246     def link(self):
247         return reverse('update-directory')
248 
249     description = _(u'Project updates for all Akvo RSR projects')
250 
251     def items(self):
252         # Limited to 50 items to prevent gateway timeouts.
253         return ProjectUpdate.objects.select_related().order_by('-id')[:50]
254 
255     def item_title(self, item):
256         return _(
257             u'Project %(project_id)d - %(project_title)s: %(update_title)s'
258         ) % {
259             'project_id': item.project.id,
260             'project_title': item.project.title,
261             'update_title': item.title
262         }
263
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/akvo/rsr/feeds.py b/akvo/rsr/feeds.py
--- a/akvo/rsr/feeds.py
+++ b/akvo/rsr/feeds.py
@@ -185,7 +185,7 @@
 
 
 class ProjectUpdates(UpdateFeed):
-    """RSS feed for last 50 RSR updates of a project."""
+    """RSS feed for last 25 RSR updates of a project."""
     def get_object(self, request, project_id):
         return Project.objects.get(pk__exact=project_id)
 
@@ -201,12 +201,12 @@
         }
 
     def items(self, obj):
-        # Limited to 50 items to prevent gateway timeouts.
-        return ProjectUpdate.objects.filter(project__id=obj.id).order_by('-id')[:50]
+        # Limited to 25 items to prevent gateway timeouts.
+        return ProjectUpdate.objects.filter(project__id=obj.id).order_by('-id')[:25]
 
 
 class OrganisationUpdates(UpdateFeed):
-    """RSS feed for last 50 RSR updates of an organisation."""
+    """RSS feed for last 25 RSR updates of an organisation."""
     feed_type = RSRMediaRssFeed
 
     def get_object(self, request, org_id):
@@ -226,8 +226,8 @@
             ) % {'org_name': obj.name, 'long_name': obj.long_name}
 
     def items(self, obj):
-        # Limited to 50 items to prevent gateway timeouts.
-        return obj.published_projects().all_updates()[:50]
+        # Limited to 25 items to prevent gateway timeouts.
+        return obj.published_projects().all_updates()[:25]
 
     def item_title(self, item):
         return _(
@@ -240,8 +240,8 @@
 
 
 class AllProjectUpdates(UpdateFeed):
-    """RSS feed for last 50 RSR updates."""
-    title = _(u'Last 50 RSR project updates')
+    """RSS feed for last 25 RSR updates."""
+    title = _(u'Last 25 RSR project updates')
     
     def link(self):
         return reverse('update-directory')
@@ -249,8 +249,8 @@
     description = _(u'Project updates for all Akvo RSR projects')
 
     def items(self):
-        # Limited to 50 items to prevent gateway timeouts.
-        return ProjectUpdate.objects.select_related().order_by('-id')[:50]
+        # Limited to 25 items to prevent gateway timeouts.
+        return ProjectUpdate.objects.select_related().order_by('-id')[:25]
 
     def item_title(self, item):
         return _(

{"golden_diff": "diff --git a/akvo/rsr/feeds.py b/akvo/rsr/feeds.py\n--- a/akvo/rsr/feeds.py\n+++ b/akvo/rsr/feeds.py\n@@ -185,7 +185,7 @@\n \n \n class ProjectUpdates(UpdateFeed):\n-    \"\"\"RSS feed for last 50 RSR updates of a project.\"\"\"\n+    \"\"\"RSS feed for last 25 RSR updates of a project.\"\"\"\n     def get_object(self, request, project_id):\n         return Project.objects.get(pk__exact=project_id)\n \n@@ -201,12 +201,12 @@\n         }\n \n     def items(self, obj):\n-        # Limited to 50 items to prevent gateway timeouts.\n-        return ProjectUpdate.objects.filter(project__id=obj.id).order_by('-id')[:50]\n+        # Limited to 25 items to prevent gateway timeouts.\n+        return ProjectUpdate.objects.filter(project__id=obj.id).order_by('-id')[:25]\n \n \n class OrganisationUpdates(UpdateFeed):\n-    \"\"\"RSS feed for last 50 RSR updates of an organisation.\"\"\"\n+    \"\"\"RSS feed for last 25 RSR updates of an organisation.\"\"\"\n     feed_type = RSRMediaRssFeed\n \n     def get_object(self, request, org_id):\n@@ -226,8 +226,8 @@\n             ) % {'org_name': obj.name, 'long_name': obj.long_name}\n \n     def items(self, obj):\n-        # Limited to 50 items to prevent gateway timeouts.\n-        return obj.published_projects().all_updates()[:50]\n+        # Limited to 25 items to prevent gateway timeouts.\n+        return obj.published_projects().all_updates()[:25]\n \n     def item_title(self, item):\n         return _(\n@@ -240,8 +240,8 @@\n \n \n class AllProjectUpdates(UpdateFeed):\n-    \"\"\"RSS feed for last 50 RSR updates.\"\"\"\n-    title = _(u'Last 50 RSR project updates')\n+    \"\"\"RSS feed for last 25 RSR updates.\"\"\"\n+    title = _(u'Last 25 RSR project updates')\n     \n     def link(self):\n         return reverse('update-directory')\n@@ -249,8 +249,8 @@\n     description = _(u'Project updates for all Akvo RSR projects')\n \n     def items(self):\n-        # Limited to 50 items to prevent gateway timeouts.\n-        return ProjectUpdate.objects.select_related().order_by('-id')[:50]\n+        # Limited to 25 items to prevent gateway timeouts.\n+        return ProjectUpdate.objects.select_related().order_by('-id')[:25]\n \n     def item_title(self, item):\n         return _(\n", "issue": "RSS feeds give a 504\nFor example: \n- http://rsr.akvo.org/rss/org-updates/273/\n- http://rsr.akvo.org/rss/updates/788/\n\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\n\n# Akvo RSR is covered by the GNU Affero General Public License.\n# See more details in the license.txt file located at the root folder of the Akvo RSR module. \n# For additional details on the GNU license please see < http://www.gnu.org/licenses/agpl.html >.\n\nimport re\n\nfrom xml.sax.saxutils import XMLGenerator\n\nfrom django.contrib.syndication.views import FeedDoesNotExist, Feed\nfrom django.core.urlresolvers import reverse\nfrom django.shortcuts import get_object_or_404\nfrom django.utils.feedgenerator import Rss201rev2Feed\nfrom django.utils.translation import ugettext_lazy as _\n\nfrom akvo.rsr.models import Project, ProjectUpdate, Organisation\n\n\ndef __dict_replace(s, d):\n    \"\"\"Replace substrings of a string using a dictionary.\"\"\"\n    for key, value in d.items():\n        s = s.replace(key, value)\n    return s\n\ndef __escape(data, entities):\n    # must do ampersand first\n    data = data.replace(\"&\", \"&amp;\")\n    data  = data.replace(\">\", \"&gt;\")\n    data  = data.replace(\"<\", \"&lt;\")\n    if entities:\n        data = __dict_replace(data, entities)\n    return data\n\ndef escape(data, entities={}):\n    \"\"\"Modification to xml.sax.saxutils.escape to that detects CDATA blocks that are not escaped\n\n    Escape &, <, and > in a string of data.\n\n    You can escape other strings of data by passing a dictionary as\n    the optional entities parameter.  The keys and values must all be\n    strings; each key will be replaced with its corresponding value.\n\n    \"\"\"\n    # find character data, re.DOTALL includes linefeed in .\n    pattern = re.compile('<!\\[CDATA\\[.*\\]\\]>', re.DOTALL)\n    iterator = pattern.finditer(data)\n    start = 0\n    bits = []\n    for match in iterator:\n        #grab chunk before first match\n        bit = data[start:match.span()[0]]\n        bit = __escape(bit, entities)\n        bits.append(bit)\n        #grab match\n        bit = data[match.span()[0]:match.span()[1]]\n        bits.extend(bit)\n        start = match.span()[1]\n    # escape tail bit after last match\n    bit = data[start:]\n    bit = __escape(bit, entities)\n    bits.extend(bit)\n    data = ''.join(bits)\n    return data\n\n\nclass RSRSimplerXMLGenerator(XMLGenerator):\n    \"\"\"subclassed to be able to call custom escape() function, see above\n    \"\"\"\n    def characters(self, content):\n        self._write(escape(content))\n\n    def addQuickElement(self, name, contents=None, attrs=None):\n        \"Convenience method for adding an element with no children\"\n        if attrs is None: attrs = {}\n        self.startElement(name, attrs)\n        if contents is not None:\n            self.characters(contents)\n        self.endElement(name)\n\n\nclass RSRMediaRssFeed(Rss201rev2Feed):\n    def rss_attributes(self):\n        attrs = super(RSRMediaRssFeed, self).rss_attributes()\n        attrs['xmlns:media']    = 'http://search.yahoo.com/mrss/'\n        attrs['xmlns:atom']     = 'http://www.w3.org/2005/Atom'\n        return attrs\n\n    def add_item_elements(self, handler, item):\n        \"\"\"Callback to add elements to each item (item/entry) element.\"\"\"\n        super(RSRMediaRssFeed, self).add_item_elements(handler, item)\n\n        if 'media:title' in item:\n            handler.addQuickElement(u\"media:title\", item['title'])\n        if 'media:description' in item:\n            handler.addQuickElement(u\"media:description\", item['media:description'])\n        if 'media:credit' in item:\n            handler.addQuickElement(u\"media:credit\", item['media:credit'])\n\n        if 'content_url' in item:\n            content = dict(url=item['content_url'])\n            if 'content_width' in item:\n                content['width'] = str(item['content_width'])\n            if 'content_height' in item:\n                content['height'] = str(item['content_height'])\n            handler.addQuickElement(u\"media:content\", '', content)\n\n        if 'thumbnail_url' in item:\n            thumbnail = dict(url=item['thumbnail_url'])\n            if 'thumbnail_width' in item:\n                thumbnail['width'] = str(item['thumbnail_width'])\n            if 'thumbnail_height' in item:\n                thumbnail['height'] = str(item['thumbnail_height'])\n            handler.addQuickElement(u\"media:thumbnail\", '', thumbnail)\n\n        if 'keywords' in item:\n            handler.addQuickElement(u\"media:keywords\", item['keywords'])\n\n    def write(self, outfile, encoding):\n        handler = RSRSimplerXMLGenerator(outfile, encoding)\n        handler.startDocument()\n        handler.startElement(u\"rss\", self.rss_attributes())\n        handler.startElement(u\"channel\", self.root_attributes())\n        self.add_root_elements(handler)\n        self.write_items(handler)\n        self.endChannelElement(handler)\n        handler.endElement(u\"rss\")\n\nclass UpdateFeed(Feed):\n    \"\"\"base class generating Update feeds\n    \"\"\"\n    feed_type = RSRMediaRssFeed\n\n    def link(self, obj):\n        if not obj:\n            raise FeedDoesNotExist\n        return obj.get_absolute_url()\n\n    def item_link(self, item):\n        return item.get_absolute_url()\n\n    def item_title(self, item):\n        return item.title\n\n    def item_description(self, item):\n        try:\n            size = item.photo.size\n            return '<![CDATA[<p><a href=\"%s\"><img src=\"%s\" alt=\"\" /></a></p><p>%s</p>]]>' % (\n                item.get_absolute_url(),\n                item.photo.thumbnail.absolute_url,\n                item.text,\n            )\n        except:\n            return item.text\n\n    def item_pubdate(self, item):\n        return item.created_at\n\n    def item_author_name(self, item):\n        return item.user.get_full_name()\n\n    def item_credit(self, item):\n        return item.photo_credit\n\n    def item_extra_kwargs(self, item):\n        \"\"\"return a dictionary to the feedgenerator for each item to be added to the feed.\n        \"\"\"\n        try:\n            size = item.photo.size\n            photo = item.photo\n            kwargs = {\n                'media:title': item.title,\n                'media:description': item.photo_caption,\n                'media:credit': item.photo_credit,\n                'content_url': photo.url,\n                'content_width': photo.width,\n                'content_height': photo.height,\n                'thumbnail_url': photo.thumbnail.absolute_url,\n                'thumbnail_width': photo.thumbnail.width(),\n                'thumbnail_height': photo.thumbnail.height(),\n            }\n            return kwargs\n        except:\n            return {}\n\n\nclass ProjectUpdates(UpdateFeed):\n    \"\"\"RSS feed for last 50 RSR updates of a project.\"\"\"\n    def get_object(self, request, project_id):\n        return Project.objects.get(pk__exact=project_id)\n\n    def title(self, obj):\n        return _(u'Akvo RSR project %(id)d: %(project_title)s') % {\n            'id': obj.id,\n            'project_title': obj.title\n        }\n\n    def description(self, obj):\n        return _(u'Project updates for project %(project_title)s') % {\n            'project_title': obj.title\n        }\n\n    def items(self, obj):\n        # Limited to 50 items to prevent gateway timeouts.\n        return ProjectUpdate.objects.filter(project__id=obj.id).order_by('-id')[:50]\n\n\nclass OrganisationUpdates(UpdateFeed):\n    \"\"\"RSS feed for last 50 RSR updates of an organisation.\"\"\"\n    feed_type = RSRMediaRssFeed\n\n    def get_object(self, request, org_id):\n        return get_object_or_404(Organisation, id=int(org_id))\n\n    def title(self, obj):\n        return _(u'Projects of %(org_name)s') % {'org_name':obj.name,}\n\n    def description(self, obj):\n        if obj.name == obj.long_name:\n            return _(u\"Project updates for projects partnered by %(org_name)s\") % {\n                'org_name': obj.name\n            }\n        else:\n            return _(\n                u\"Project updates for projects partnered by %(org_name)s - %(long_name)s\"\n            ) % {'org_name': obj.name, 'long_name': obj.long_name}\n\n    def items(self, obj):\n        # Limited to 50 items to prevent gateway timeouts.\n        return obj.published_projects().all_updates()[:50]\n\n    def item_title(self, item):\n        return _(\n            u'Project %(project_id)d - %(project_title)s: %(update_title)s'\n        ) % {\n            'project_id': item.project.id,\n            'project_title': item.project.title,\n            'update_title': item.title\n        }\n\n\nclass AllProjectUpdates(UpdateFeed):\n    \"\"\"RSS feed for last 50 RSR updates.\"\"\"\n    title = _(u'Last 50 RSR project updates')\n    \n    def link(self):\n        return reverse('update-directory')\n\n    description = _(u'Project updates for all Akvo RSR projects')\n\n    def items(self):\n        # Limited to 50 items to prevent gateway timeouts.\n        return ProjectUpdate.objects.select_related().order_by('-id')[:50]\n\n    def item_title(self, item):\n        return _(\n            u'Project %(project_id)d - %(project_title)s: %(update_title)s'\n        ) % {\n            'project_id': item.project.id,\n            'project_title': item.project.title,\n            'update_title': item.title\n        }\n", "path": "akvo/rsr/feeds.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\n\n# Akvo RSR is covered by the GNU Affero General Public License.\n# See more details in the license.txt file located at the root folder of the Akvo RSR module. \n# For additional details on the GNU license please see < http://www.gnu.org/licenses/agpl.html >.\n\nimport re\n\nfrom xml.sax.saxutils import XMLGenerator\n\nfrom django.contrib.syndication.views import FeedDoesNotExist, Feed\nfrom django.core.urlresolvers import reverse\nfrom django.shortcuts import get_object_or_404\nfrom django.utils.feedgenerator import Rss201rev2Feed\nfrom django.utils.translation import ugettext_lazy as _\n\nfrom akvo.rsr.models import Project, ProjectUpdate, Organisation\n\n\ndef __dict_replace(s, d):\n    \"\"\"Replace substrings of a string using a dictionary.\"\"\"\n    for key, value in d.items():\n        s = s.replace(key, value)\n    return s\n\ndef __escape(data, entities):\n    # must do ampersand first\n    data = data.replace(\"&\", \"&amp;\")\n    data  = data.replace(\">\", \"&gt;\")\n    data  = data.replace(\"<\", \"&lt;\")\n    if entities:\n        data = __dict_replace(data, entities)\n    return data\n\ndef escape(data, entities={}):\n    \"\"\"Modification to xml.sax.saxutils.escape to that detects CDATA blocks that are not escaped\n\n    Escape &, <, and > in a string of data.\n\n    You can escape other strings of data by passing a dictionary as\n    the optional entities parameter.  The keys and values must all be\n    strings; each key will be replaced with its corresponding value.\n\n    \"\"\"\n    # find character data, re.DOTALL includes linefeed in .\n    pattern = re.compile('<!\\[CDATA\\[.*\\]\\]>', re.DOTALL)\n    iterator = pattern.finditer(data)\n    start = 0\n    bits = []\n    for match in iterator:\n        #grab chunk before first match\n        bit = data[start:match.span()[0]]\n        bit = __escape(bit, entities)\n        bits.append(bit)\n        #grab match\n        bit = data[match.span()[0]:match.span()[1]]\n        bits.extend(bit)\n        start = match.span()[1]\n    # escape tail bit after last match\n    bit = data[start:]\n    bit = __escape(bit, entities)\n    bits.extend(bit)\n    data = ''.join(bits)\n    return data\n\n\nclass RSRSimplerXMLGenerator(XMLGenerator):\n    \"\"\"subclassed to be able to call custom escape() function, see above\n    \"\"\"\n    def characters(self, content):\n        self._write(escape(content))\n\n    def addQuickElement(self, name, contents=None, attrs=None):\n        \"Convenience method for adding an element with no children\"\n        if attrs is None: attrs = {}\n        self.startElement(name, attrs)\n        if contents is not None:\n            self.characters(contents)\n        self.endElement(name)\n\n\nclass RSRMediaRssFeed(Rss201rev2Feed):\n    def rss_attributes(self):\n        attrs = super(RSRMediaRssFeed, self).rss_attributes()\n        attrs['xmlns:media']    = 'http://search.yahoo.com/mrss/'\n        attrs['xmlns:atom']     = 'http://www.w3.org/2005/Atom'\n        return attrs\n\n    def add_item_elements(self, handler, item):\n        \"\"\"Callback to add elements to each item (item/entry) element.\"\"\"\n        super(RSRMediaRssFeed, self).add_item_elements(handler, item)\n\n        if 'media:title' in item:\n            handler.addQuickElement(u\"media:title\", item['title'])\n        if 'media:description' in item:\n            handler.addQuickElement(u\"media:description\", item['media:description'])\n        if 'media:credit' in item:\n            handler.addQuickElement(u\"media:credit\", item['media:credit'])\n\n        if 'content_url' in item:\n            content = dict(url=item['content_url'])\n            if 'content_width' in item:\n                content['width'] = str(item['content_width'])\n            if 'content_height' in item:\n                content['height'] = str(item['content_height'])\n            handler.addQuickElement(u\"media:content\", '', content)\n\n        if 'thumbnail_url' in item:\n            thumbnail = dict(url=item['thumbnail_url'])\n            if 'thumbnail_width' in item:\n                thumbnail['width'] = str(item['thumbnail_width'])\n            if 'thumbnail_height' in item:\n                thumbnail['height'] = str(item['thumbnail_height'])\n            handler.addQuickElement(u\"media:thumbnail\", '', thumbnail)\n\n        if 'keywords' in item:\n            handler.addQuickElement(u\"media:keywords\", item['keywords'])\n\n    def write(self, outfile, encoding):\n        handler = RSRSimplerXMLGenerator(outfile, encoding)\n        handler.startDocument()\n        handler.startElement(u\"rss\", self.rss_attributes())\n        handler.startElement(u\"channel\", self.root_attributes())\n        self.add_root_elements(handler)\n        self.write_items(handler)\n        self.endChannelElement(handler)\n        handler.endElement(u\"rss\")\n\nclass UpdateFeed(Feed):\n    \"\"\"base class generating Update feeds\n    \"\"\"\n    feed_type = RSRMediaRssFeed\n\n    def link(self, obj):\n        if not obj:\n            raise FeedDoesNotExist\n        return obj.get_absolute_url()\n\n    def item_link(self, item):\n        return item.get_absolute_url()\n\n    def item_title(self, item):\n        return item.title\n\n    def item_description(self, item):\n        try:\n            size = item.photo.size\n            return '<![CDATA[<p><a href=\"%s\"><img src=\"%s\" alt=\"\" /></a></p><p>%s</p>]]>' % (\n                item.get_absolute_url(),\n                item.photo.thumbnail.absolute_url,\n                item.text,\n            )\n        except:\n            return item.text\n\n    def item_pubdate(self, item):\n        return item.created_at\n\n    def item_author_name(self, item):\n        return item.user.get_full_name()\n\n    def item_credit(self, item):\n        return item.photo_credit\n\n    def item_extra_kwargs(self, item):\n        \"\"\"return a dictionary to the feedgenerator for each item to be added to the feed.\n        \"\"\"\n        try:\n            size = item.photo.size\n            photo = item.photo\n            kwargs = {\n                'media:title': item.title,\n                'media:description': item.photo_caption,\n                'media:credit': item.photo_credit,\n                'content_url': photo.url,\n                'content_width': photo.width,\n                'content_height': photo.height,\n                'thumbnail_url': photo.thumbnail.absolute_url,\n                'thumbnail_width': photo.thumbnail.width(),\n                'thumbnail_height': photo.thumbnail.height(),\n            }\n            return kwargs\n        except:\n            return {}\n\n\nclass ProjectUpdates(UpdateFeed):\n    \"\"\"RSS feed for last 25 RSR updates of a project.\"\"\"\n    def get_object(self, request, project_id):\n        return Project.objects.get(pk__exact=project_id)\n\n    def title(self, obj):\n        return _(u'Akvo RSR project %(id)d: %(project_title)s') % {\n            'id': obj.id,\n            'project_title': obj.title\n        }\n\n    def description(self, obj):\n        return _(u'Project updates for project %(project_title)s') % {\n            'project_title': obj.title\n        }\n\n    def items(self, obj):\n        # Limited to 25 items to prevent gateway timeouts.\n        return ProjectUpdate.objects.filter(project__id=obj.id).order_by('-id')[:25]\n\n\nclass OrganisationUpdates(UpdateFeed):\n    \"\"\"RSS feed for last 25 RSR updates of an organisation.\"\"\"\n    feed_type = RSRMediaRssFeed\n\n    def get_object(self, request, org_id):\n        return get_object_or_404(Organisation, id=int(org_id))\n\n    def title(self, obj):\n        return _(u'Projects of %(org_name)s') % {'org_name':obj.name,}\n\n    def description(self, obj):\n        if obj.name == obj.long_name:\n            return _(u\"Project updates for projects partnered by %(org_name)s\") % {\n                'org_name': obj.name\n            }\n        else:\n            return _(\n                u\"Project updates for projects partnered by %(org_name)s - %(long_name)s\"\n            ) % {'org_name': obj.name, 'long_name': obj.long_name}\n\n    def items(self, obj):\n        # Limited to 25 items to prevent gateway timeouts.\n        return obj.published_projects().all_updates()[:25]\n\n    def item_title(self, item):\n        return _(\n            u'Project %(project_id)d - %(project_title)s: %(update_title)s'\n        ) % {\n            'project_id': item.project.id,\n            'project_title': item.project.title,\n            'update_title': item.title\n        }\n\n\nclass AllProjectUpdates(UpdateFeed):\n    \"\"\"RSS feed for last 25 RSR updates.\"\"\"\n    title = _(u'Last 25 RSR project updates')\n    \n    def link(self):\n        return reverse('update-directory')\n\n    description = _(u'Project updates for all Akvo RSR projects')\n\n    def items(self):\n        # Limited to 25 items to prevent gateway timeouts.\n        return ProjectUpdate.objects.select_related().order_by('-id')[:25]\n\n    def item_title(self, item):\n        return _(\n            u'Project %(project_id)d - %(project_title)s: %(update_title)s'\n        ) % {\n            'project_id': item.project.id,\n            'project_title': item.project.title,\n            'update_title': item.title\n        }\n", "path": "akvo/rsr/feeds.py"}]}