c6307d81d017e075537c456fd473b83543f0fddb
[mygpo.git] / mygpo / web / views / device.py
blobc6307d81d017e075537c456fd473b83543f0fddb
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 functools import wraps
19 from xml.parsers.expat import ExpatError
21 from django.shortcuts import render
22 from django.core.urlresolvers import reverse
23 from django.http import HttpResponseRedirect, HttpResponseBadRequest, \
24 HttpResponseNotFound
25 from django.contrib import messages
26 from mygpo.web.forms import DeviceForm, SyncForm
27 from mygpo.web.utils import symbian_opml_changes
28 from django.utils.translation import ugettext as _
29 from django.contrib.auth.decorators import login_required
30 from django.views.decorators.vary import vary_on_cookie
31 from django.views.decorators.cache import never_cache, cache_control
33 from restkit.errors import Unauthorized
35 from mygpo.api import simple
36 from mygpo.decorators import allowed_methods, repeat_on_conflict
37 from mygpo.users.models import Device, DeviceUIDException, \
38 DeviceDoesNotExist
39 from mygpo.users.tasks import sync_user
40 from mygpo.db.couchdb.podcast_state import podcast_states_for_device
43 @vary_on_cookie
44 @cache_control(private=True)
45 @login_required
46 def overview(request):
48 device_groups = request.user.get_grouped_devices()
49 deleted_devices = request.user.inactive_devices
51 return render(request, 'devicelist.html', {
52 'device_groups': device_groups,
53 'deleted_devices': deleted_devices,
58 def device_decorator(f):
59 @login_required
60 @vary_on_cookie
61 @cache_control(private=True)
62 @wraps(f)
63 def _decorator(request, uid, *args, **kwargs):
65 try:
66 device = request.user.get_device_by_uid(uid, only_active=False)
68 except DeviceDoesNotExist as e:
69 return HttpResponseNotFound(str(e))
71 return f(request, device, *args, **kwargs)
73 return _decorator
77 @login_required
78 @device_decorator
79 def show(request, device):
81 request.user.sync_group(device)
83 subscriptions = list(device.get_subscribed_podcasts())
84 synced_with = request.user.get_synced(device)
86 sync_targets = list(request.user.get_sync_targets(device))
87 sync_form = SyncForm()
88 sync_form.set_targets(sync_targets,
89 _('Synchronize with the following devices'))
91 return render(request, 'device.html', {
92 'device': device,
93 'sync_form': sync_form,
94 'subscriptions': subscriptions,
95 'synced_with': synced_with,
96 'has_sync_targets': len(sync_targets) > 0,
100 @login_required
101 @never_cache
102 @allowed_methods(['POST'])
103 def create(request):
104 device_form = DeviceForm(request.POST)
106 if not device_form.is_valid():
108 messages.error(request, _('Please fill out all fields.'))
110 return HttpResponseRedirect(reverse('device-edit-new'))
113 device = Device()
114 device.name = device_form.cleaned_data['name']
115 device.type = device_form.cleaned_data['type']
116 device.uid = device_form.cleaned_data['uid'].replace(' ', '-')
117 try:
118 request.user.set_device(device)
119 request.user.save()
120 messages.success(request, _('Device saved'))
122 except DeviceUIDException as e:
123 messages.error(request, _(unicode(e)))
125 return render(request, 'device-create.html', {
126 'device': device,
127 'device_form': device_form,
130 except Unauthorized:
131 messages.error(request, _("You can't use the same Device "
132 "ID for two devices."))
134 return render(request, 'device-create.html', {
135 'device': device,
136 'device_form': device_form,
140 return HttpResponseRedirect(reverse('device-edit', args=[device.uid]))
144 @device_decorator
145 @login_required
146 @allowed_methods(['POST'])
147 def update(request, device):
148 device_form = DeviceForm(request.POST)
150 uid = device.uid
152 if device_form.is_valid():
154 device.name = device_form.cleaned_data['name']
155 device.type = device_form.cleaned_data['type']
156 device.uid = device_form.cleaned_data['uid'].replace(' ', '-')
157 try:
158 request.user.update_device(device)
159 messages.success(request, _('Device updated'))
160 uid = device.uid # accept the new UID after rest has succeeded
162 except DeviceUIDException as e:
163 messages.error(request, _(str(e)))
165 except Unauthorized as u:
166 messages.error(request, _("You can't use the same Device "
167 "ID for two devices."))
169 return HttpResponseRedirect(reverse('device-edit', args=[uid]))
172 @login_required
173 @vary_on_cookie
174 @cache_control(private=True)
175 @allowed_methods(['GET'])
176 def edit_new(request):
178 device = Device()
180 device_form = DeviceForm({
181 'name': device.name,
182 'type': device.type,
183 'uid': device.uid
186 return render(request, 'device-create.html', {
187 'device': device,
188 'device_form': device_form,
194 @device_decorator
195 @login_required
196 @allowed_methods(['GET'])
197 def edit(request, device):
199 device_form = DeviceForm({
200 'name': device.name,
201 'type': device.type,
202 'uid': device.uid
205 synced_with = request.user.get_synced(device)
207 sync_targets = list(request.user.get_sync_targets(device))
208 sync_form = SyncForm()
209 sync_form.set_targets(sync_targets,
210 _('Synchronize with the following devices'))
212 return render(request, 'device-edit.html', {
213 'device': device,
214 'device_form': device_form,
215 'sync_form': sync_form,
216 'synced_with': synced_with,
217 'has_sync_targets': len(sync_targets) > 0,
221 @device_decorator
222 @login_required
223 def upload_opml(request, device):
225 if not 'opml' in request.FILES:
226 return HttpResponseRedirect(reverse('device-edit', args=[device.uid]))
228 opml = request.FILES['opml'].read()
230 try:
231 subscriptions = simple.parse_subscription(opml, 'opml')
232 simple.set_subscriptions(subscriptions, request.user, device.uid, None)
234 except ExpatError as ee:
235 msg = _('Could not upload subscriptions: {err}').format(err=str(ee))
236 messages.error(request, msg)
237 return HttpResponseRedirect(reverse('device-edit', args=[device.uid]))
239 return HttpResponseRedirect(reverse('device', args=[device.uid]))
242 @device_decorator
243 @login_required
244 def opml(request, device):
245 response = simple.format_podcast_list(simple.get_subscriptions(request.user, device.uid), 'opml', request.user.username)
246 response['Content-Disposition'] = 'attachment; filename=%s.opml' % device.uid
247 return response
250 @device_decorator
251 @login_required
252 def symbian_opml(request, device):
253 subscriptions = simple.get_subscriptions(request.user, device.uid)
254 subscriptions = map(symbian_opml_changes, subscriptions)
256 response = simple.format_podcast_list(subscriptions, 'opml', request.user.username)
257 response['Content-Disposition'] = 'attachment; filename=%s.opml' % device.uid
258 return response
261 @device_decorator
262 @login_required
263 @allowed_methods(['POST'])
264 def delete(request, device):
266 @repeat_on_conflict(['user'])
267 def _delete(user, device):
268 if request.user.is_synced(device):
269 request.user.unsync_device(device)
270 device.deleted = True
271 user.set_device(device)
272 if user.is_synced(device):
273 user.unsync_device(device)
274 user.save()
276 _delete(user=request.user, device=device)
278 return HttpResponseRedirect(reverse('devices'))
281 @login_required
282 @device_decorator
283 def delete_permanently(request, device):
285 @repeat_on_conflict(['state'])
286 def remove_device(state, dev):
287 state.remove_device(dev)
288 state.save()
290 states = podcast_states_for_device(device.id)
291 for state in states:
292 remove_device(state=state, dev=device)
294 @repeat_on_conflict(['user'])
295 def _remove(user, device):
296 user.remove_device(device)
297 user.save()
299 _remove(user=request.user, device=device)
301 return HttpResponseRedirect(reverse('devices'))
303 @device_decorator
304 @login_required
305 def undelete(request, device):
307 device.deleted = False
308 request.user.update_device(device)
310 return HttpResponseRedirect(reverse('device', args=[device.uid]))
313 @device_decorator
314 @login_required
315 @allowed_methods(['POST'])
316 def sync(request, device):
318 form = SyncForm(request.POST)
319 if not form.is_valid():
320 return HttpResponseBadRequest('invalid')
323 @repeat_on_conflict(['user'])
324 def do_sync(user, device, sync_target):
325 user.sync_devices(device, sync_target)
326 user.save()
329 try:
330 target_uid = form.get_target()
331 sync_target = request.user.get_device_by_uid(target_uid)
332 do_sync(user=request.user, device=device, sync_target=sync_target)
334 except DeviceDoesNotExist as e:
335 messages.error(request, str(e))
337 sync_user.delay(request.user)
339 return HttpResponseRedirect(reverse('device', args=[device.uid]))
342 @device_decorator
343 @login_required
344 @allowed_methods(['GET'])
345 def unsync(request, device):
347 @repeat_on_conflict(['user'])
348 def do_unsync(user, device):
349 user.unsync_device(device)
350 user.save()
352 try:
353 do_unsync(user=request.user, device=device)
355 except ValueError, e:
356 messages.error(request, 'Could not unsync the device: {err}'.format(
357 err=str(e)))
359 return HttpResponseRedirect(reverse('device', args=[device.uid]))
362 from mygpo.web import views
363 history = views.history