simplify Device creation
[mygpo.git] / mygpo / api / simple.py
blob5db6e62cf172f6fbe364ba9617c3dad614445216
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
20 from mygpo.api.models import Device, Podcast, 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 _
29 from mygpo.decorators import allowed_methods
32 try:
33 import json
35 # Python 2.5 seems to have a different json module
36 if not 'dumps' in dir(json):
37 raise ImportError
39 except ImportError:
40 import simplejson as json
43 ALLOWED_FORMATS = ('txt', 'opml', 'json')
45 def check_format(fn):
46 def tmp(request, format, *args, **kwargs):
47 if not format in ALLOWED_FORMATS:
48 return HttpResponseBadRequest('Invalid format')
50 return fn(request, *args, format=format, **kwargs)
51 return tmp
54 @csrf_exempt
55 @require_valid_user
56 @check_username
57 @check_format
58 @allowed_methods(['GET', 'PUT', 'POST'])
59 def subscriptions(request, username, device_uid, format):
61 if request.method == 'GET':
62 title = _('%(username)s\'s Subscription List') % {'username': username}
63 subscriptions = get_subscriptions(request.user, device_uid)
64 return format_podcast_list(subscriptions, format, title)
66 elif request.method in ('PUT', 'POST'):
67 subscriptions = parse_subscription(request.raw_post_data, format)
68 return set_subscriptions(subscriptions, request.user, device_uid)
71 @csrf_exempt
72 @require_valid_user
73 @check_username
74 @check_format
75 @allowed_methods(['GET'])
76 def all_subscriptions(request, username, format):
77 subscriptions = get_all_subscriptions(request.user)
78 title = _('%(username)s\'s Subscription List') % {'username': username}
79 return format_podcast_list(subscriptions, format, title)
82 def format_podcast_list(obj_list, format, title, get_podcast=lambda x: x, json_map=lambda x: x.url):
83 """
84 Formats a list of podcasts for use in a API response
86 obj_list is a list of podcasts or objects that contain podcasts
87 format is one if txt, opml or json
88 title is a label of the list
89 if obj_list is a list of objects containing podcasts, get_podcast is the
90 function used to get the podcast out of the each of these objects
91 json_map is a function returning the contents of an object (from obj_list)
92 that should be contained in the result (only used for format='json')
93 """
94 if format == 'txt':
95 podcasts = map(get_podcast, obj_list)
96 s = '\n'.join([p.url for p in podcasts] + [''])
97 return HttpResponse(s, mimetype='text/plain')
99 elif format == 'opml':
100 podcasts = map(get_podcast, obj_list)
101 exporter = Exporter(title)
102 opml = exporter.generate(podcasts)
103 return HttpResponse(opml, mimetype='text/xml')
105 elif format == 'json':
106 objs = map(json_map, obj_list)
107 return JsonResponse(objs)
109 else:
110 return None
113 def get_subscriptions(user, device_uid):
114 device = get_object_or_404(Device, uid=device_uid, user=user, deleted=False)
115 return [s.podcast for s in device.get_subscriptions()]
118 def parse_subscription(raw_post_data, format):
119 if format == 'txt':
120 urls = raw_post_data.split('\n')
122 elif format == 'opml':
123 begin = raw_post_data.find('<?xml')
124 end = raw_post_data.find('</opml>') + 7
125 i = Importer(content=raw_post_data[begin:end])
126 urls = [p['url'] for p in i.items]
128 elif format == 'json':
129 begin = raw_post_data.find('[')
130 end = raw_post_data.find(']') + 1
131 urls = json.loads(raw_post_data[begin:end])
133 else:
134 return []
136 urls = map(sanitize_url, urls)
137 urls = filter(lambda x: x, urls)
138 urls = set(urls)
139 return urls
142 def set_subscriptions(urls, user, device_uid):
143 device, created = Device.objects.get_or_create(user=user, uid=device_uid)
145 # undelete a previously deleted device
146 if device.deleted:
147 device.deleted = False
148 device.save()
150 old = [s.podcast.url for s in device.get_subscriptions()]
151 new = [p for p in urls if p not in old]
152 rem = [p for p in old if p not in urls]
154 for r in rem:
155 p = Podcast.objects.get(url=r)
156 p.unsubscribe(device)
157 s.save()
159 for n in new:
160 p, created = Podcast.objects.get_or_create(url=n)
161 p.subscribe(device)
162 s.save()
164 # Only an empty response is a successful response
165 return HttpResponse('', mimetype='text/plain')
168 @check_format
169 @allowed_methods(['GET'])
170 def toplist(request, count, format):
171 if int(count) not in range(1,100):
172 count = 100
174 toplist = get_toplist(count)
175 json_map = lambda t: {'url': t.get_podcast().url,
176 'title':t.get_podcast().title,
177 'description':t.get_podcast().description,
178 'subscribers':t.subscriptions,
179 'subscribers_last_week':t.oldplace}
180 title = _('gpodder.net - Top %(count)d') % {'count': len(toplist)}
181 return format_podcast_list(toplist,
182 format,
183 title,
184 get_podcast=lambda x: x.get_podcast(),
185 json_map=json_map)
188 @check_format
189 @allowed_methods(['GET'])
190 def search(request, format):
191 query = request.GET.get('q', '').encode('utf-8')
193 if not query:
194 return HttpResponseBadRequest('/search.opml|txt|json?q={query}')
196 results = [r.get_podcast() for r in SearchEntry.objects.search(query)[:20]]
198 json_map = lambda p: {'url':p.url, 'title':p.title, 'description':p.description}
199 title = _('gpodder.net - Search')
200 return format_podcast_list(results, format, title, json_map=json_map)
203 @require_valid_user
204 @check_format
205 @allowed_methods(['GET'])
206 def suggestions(request, count, format):
207 if int(count) not in range(1,100):
208 count = 100
210 suggestions = SuggestionEntry.objects.for_user(user)[:int(count)]
211 json_map = lambda p: {'url': p.url, 'title': p.title, 'description': p.description}
212 title = _('gpodder.net - %(count)d Suggestions') % {'count': len(suggestions)}
213 return format_podcast_list(suggestions, format, title, json_map=json_map)