2b58d6b1594f6669db1cd19fd25f378bd26e73f1
2 # This file is part of my.gpodder.org.
4 # my.gpodder.org is free software: you can redistribute it and/or modify it
5 # under the terms of the GNU Affero General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or (at your
7 # option) any later version.
9 # my.gpodder.org is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 # License for more details.
14 # You should have received a copy of the GNU Affero General Public License
15 # along with my.gpodder.org. If not, see <http://www.gnu.org/licenses/>.
18 from mygpo
.api
.basic_auth
import require_valid_user
, check_username
19 from django
.http
import HttpResponse
, HttpResponseBadRequest
, HttpResponseNotAllowed
20 from mygpo
.api
.models
import Device
, SubscriptionAction
, Podcast
, SUBSCRIBE_ACTION
, UNSUBSCRIBE_ACTION
, SuggestionEntry
21 from mygpo
.api
.opml
import Exporter
, Importer
22 from mygpo
.api
.httpresponse
import JsonResponse
23 from mygpo
.api
.sanitizing
import sanitize_url
24 from mygpo
.api
.backend
import get_toplist
, get_all_subscriptions
25 from django
.views
.decorators
.csrf
import csrf_exempt
26 from django
.shortcuts
import get_object_or_404
27 from mygpo
.search
.models
import SearchEntry
28 from django
.utils
.translation
import ugettext
as _
34 # Python 2.5 seems to have a different json module
35 if not 'dumps' in dir(json
):
39 import simplejson
as json
42 ALLOWED_FORMATS
= ('txt', 'opml', 'json')
45 def tmp(request
, format
, *args
, **kwargs
):
46 if not format
in ALLOWED_FORMATS
:
47 return HttpResponseBadRequest('Invalid format')
49 return fn(request
, *args
, format
=format
, **kwargs
)
57 def subscriptions(request
, username
, device_uid
, format
):
59 if request
.method
== 'GET':
60 title
= _('%(username)s\'s Subscription List') % {'username': username
}
61 subscriptions
= get_subscriptions(request
.user
, device_uid
)
62 return format_podcast_list(subscriptions
, format
, title
)
64 elif request
.method
in ('PUT', 'POST'):
65 subscriptions
= parse_subscription(request
.raw_post_data
, format
)
66 return set_subscriptions(subscriptions
, request
.user
, device_uid
)
69 return HttpResponseNotAllowed(['GET', 'PUT', 'POST'])
76 def all_subscriptions(request
, username
, format
):
77 if request
.method
!= 'GET':
78 return HttpResponseNotAllowed(['GET'])
80 subscriptions
= get_all_subscriptions(request
.user
)
81 title
= _('%(username)s\'s Subscription List') % {'username': username
}
82 return format_podcast_list(subscriptions
, format
, title
)
85 def format_podcast_list(obj_list
, format
, title
, get_podcast
=lambda x
: x
, json_map
=lambda x
: x
.url
):
87 Formats a list of podcasts for use in a API response
89 obj_list is a list of podcasts or objects that contain podcasts
90 format is one if txt, opml or json
91 title is a label of the list
92 if obj_list is a list of objects containing podcasts, get_podcast is the
93 function used to get the podcast out of the each of these objects
94 json_map is a function returning the contents of an object (from obj_list)
95 that should be contained in the result (only used for format='json')
98 podcasts
= map(get_podcast
, obj_list
)
99 s
= '\n'.join([p
.url
for p
in podcasts
] + [''])
100 return HttpResponse(s
, mimetype
='text/plain')
102 elif format
== 'opml':
103 podcasts
= map(get_podcast
, obj_list
)
104 exporter
= Exporter(title
)
105 opml
= exporter
.generate(podcasts
)
106 return HttpResponse(opml
, mimetype
='text/xml')
108 elif format
== 'json':
109 objs
= map(json_map
, obj_list
)
110 return JsonResponse(objs
)
116 def get_subscriptions(user
, device_uid
):
117 device
= get_object_or_404(Device
, uid
=device_uid
, user
=user
, deleted
=False)
118 return [s
.podcast
for s
in device
.get_subscriptions()]
121 def parse_subscription(raw_post_data
, format
):
123 urls
= raw_post_data
.split('\n')
125 elif format
== 'opml':
126 begin
= raw_post_data
.find('<?xml')
127 end
= raw_post_data
.find('</opml>') + 7
128 i
= Importer(content
=raw_post_data
[begin
:end
])
129 urls
= [p
['url'] for p
in i
.items
]
131 elif format
== 'json':
132 begin
= raw_post_data
.find('[')
133 end
= raw_post_data
.find(']') + 1
134 urls
= json
.loads(raw_post_data
[begin
:end
])
139 urls
= map(sanitize_url
, urls
)
140 urls
= filter(lambda x
: x
, urls
)
145 def set_subscriptions(urls
, user
, device_uid
):
146 device
, created
= Device
.objects
.get_or_create(user
=user
, uid
=device_uid
,
147 defaults
= {'type': 'other', 'name': device_uid
})
149 # undelete a previously deleted device
151 device
.deleted
= False
154 old
= [s
.podcast
.url
for s
in device
.get_subscriptions()]
155 new
= [p
for p
in urls
if p
not in old
]
156 rem
= [p
for p
in old
if p
not in urls
]
159 p
= Podcast
.objects
.get(url
=r
)
160 s
= SubscriptionAction(podcast
=p
, device
=device
, action
=UNSUBSCRIBE_ACTION
)
164 p
, created
= Podcast
.objects
.get_or_create(url
=n
)
165 s
= SubscriptionAction(podcast
=p
, action
=SUBSCRIBE_ACTION
, device
=device
)
168 # Only an empty response is a successful response
169 return HttpResponse('', mimetype
='text/plain')
173 def toplist(request
, count
, format
):
174 if request
.method
!= 'GET':
175 return HttpResponseNotAllowed(['GET'])
177 if int(count
) not in range(1,100):
180 toplist
= get_toplist(count
)
181 json_map
= lambda t
: {'url': t
.get_podcast().url
,
182 'title':t
.get_podcast().title
,
183 'description':t
.get_podcast().description
,
184 'subscribers':t
.subscriptions
,
185 'subscribers_last_week':t
.oldplace
}
186 title
= _('gpodder.net - Top %(count)d') % {'count': len(toplist
)}
187 return format_podcast_list(toplist
,
190 get_podcast
=lambda x
: x
.get_podcast(),
195 def search(request
, format
):
196 if request
.method
!= 'GET':
197 return HttpResponseNotAllowed(['GET'])
199 query
= request
.GET
.get('q', '').encode('utf-8')
202 return HttpResponseBadRequest('/search.opml|txt|json?q={query}')
204 results
= [r
.get_podcast() for r
in SearchEntry
.objects
.search(query
)[:20]]
206 json_map
= lambda p
: {'url':p
.url
, 'title':p
.title
, 'description':p
.description
}
207 title
= _('gpodder.net - Search')
208 return format_podcast_list(results
, format
, title
, json_map
=json_map
)
213 def suggestions(request
, count
, format
):
214 if request
.method
!= 'GET':
215 return HttpResponseNotAllowed(['GET'])
217 if int(count
) not in range(1,100):
220 suggestions
= SuggestionEntry
.objects
.for_user(user
)[:int(count
)]
221 json_map
= lambda p
: {'url': p
.url
, 'title': p
.title
, 'description': p
.description
}
222 title
= _('gpodder.net - %(count)d Suggestions') % {'count': len(suggestions
)}
223 return format_podcast_list(suggestions
, format
, title
, json_map
=json_map
)