replace User.update_device with safe db access
[mygpo.git] / mygpo / web / views / device.py
blob4c82bdebfe4f0eeb1dcfc2e668daacf42e50e084
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, set_device_task_state
40 from mygpo.db.couchdb.podcast_state import podcast_states_for_device, \
41 remove_device_from_podcast_state
42 from mygpo.db.couchdb.user import set_device_deleted, unsync_device, set_device
45 @vary_on_cookie
46 @cache_control(private=True)
47 @login_required
48 def overview(request):
50 device_groups = request.user.get_grouped_devices()
51 deleted_devices = request.user.inactive_devices
53 return render(request, 'devicelist.html', {
54 'device_groups': device_groups,
55 'deleted_devices': deleted_devices,
60 def device_decorator(f):
61 @login_required
62 @vary_on_cookie
63 @cache_control(private=True)
64 @wraps(f)
65 def _decorator(request, uid, *args, **kwargs):
67 try:
68 device = request.user.get_device_by_uid(uid, only_active=False)
70 except DeviceDoesNotExist as e:
71 return HttpResponseNotFound(str(e))
73 return f(request, device, *args, **kwargs)
75 return _decorator
79 @login_required
80 @device_decorator
81 def show(request, device):
83 request.user.sync_group(device)
85 subscriptions = list(device.get_subscribed_podcasts())
86 synced_with = request.user.get_synced(device)
88 sync_targets = list(request.user.get_sync_targets(device))
89 sync_form = SyncForm()
90 sync_form.set_targets(sync_targets,
91 _('Synchronize with the following devices'))
93 return render(request, 'device.html', {
94 'device': device,
95 'sync_form': sync_form,
96 'subscriptions': subscriptions,
97 'synced_with': synced_with,
98 'has_sync_targets': len(sync_targets) > 0,
102 @login_required
103 @never_cache
104 @allowed_methods(['POST'])
105 def create(request):
106 device_form = DeviceForm(request.POST)
108 if not device_form.is_valid():
110 messages.error(request, _('Please fill out all fields.'))
112 return HttpResponseRedirect(reverse('device-edit-new'))
115 device = Device()
116 device.name = device_form.cleaned_data['name']
117 device.type = device_form.cleaned_data['type']
118 device.uid = device_form.cleaned_data['uid'].replace(' ', '-')
119 try:
120 set_device(request.user, device)
121 messages.success(request, _('Device saved'))
123 except DeviceUIDException as e:
124 messages.error(request, _(unicode(e)))
126 return render(request, 'device-create.html', {
127 'device': device,
128 'device_form': device_form,
131 except Unauthorized:
132 messages.error(request, _("You can't use the same Device "
133 "ID for two devices."))
135 return render(request, 'device-create.html', {
136 'device': device,
137 'device_form': device_form,
141 return HttpResponseRedirect(reverse('device-edit', args=[device.uid]))
145 @device_decorator
146 @login_required
147 @allowed_methods(['POST'])
148 def update(request, device):
149 device_form = DeviceForm(request.POST)
151 uid = device.uid
153 if device_form.is_valid():
155 device.name = device_form.cleaned_data['name']
156 device.type = device_form.cleaned_data['type']
157 device.uid = device_form.cleaned_data['uid'].replace(' ', '-')
158 try:
159 set_device(request.user, device)
160 messages.success(request, _('Device updated'))
161 uid = device.uid # accept the new UID after rest has succeeded
163 except DeviceUIDException as e:
164 messages.error(request, _(str(e)))
166 except Unauthorized as u:
167 messages.error(request, _("You can't use the same Device "
168 "ID for two devices."))
170 return HttpResponseRedirect(reverse('device-edit', args=[uid]))
173 @login_required
174 @vary_on_cookie
175 @cache_control(private=True)
176 @allowed_methods(['GET'])
177 def edit_new(request):
179 device = Device()
181 device_form = DeviceForm({
182 'name': device.name,
183 'type': device.type,
184 'uid': device.uid
187 return render(request, 'device-create.html', {
188 'device': device,
189 'device_form': device_form,
195 @device_decorator
196 @login_required
197 @allowed_methods(['GET'])
198 def edit(request, device):
200 device_form = DeviceForm({
201 'name': device.name,
202 'type': device.type,
203 'uid': device.uid
206 synced_with = request.user.get_synced(device)
208 sync_targets = list(request.user.get_sync_targets(device))
209 sync_form = SyncForm()
210 sync_form.set_targets(sync_targets,
211 _('Synchronize with the following devices'))
213 return render(request, 'device-edit.html', {
214 'device': device,
215 'device_form': device_form,
216 'sync_form': sync_form,
217 'synced_with': synced_with,
218 'has_sync_targets': len(sync_targets) > 0,
222 @device_decorator
223 @login_required
224 def upload_opml(request, device):
226 if not 'opml' in request.FILES:
227 return HttpResponseRedirect(reverse('device-edit', args=[device.uid]))
229 opml = request.FILES['opml'].read()
231 try:
232 subscriptions = simple.parse_subscription(opml, 'opml')
233 simple.set_subscriptions(subscriptions, request.user, device.uid, None)
235 except ExpatError as ee:
236 msg = _('Could not upload subscriptions: {err}').format(err=str(ee))
237 messages.error(request, msg)
238 return HttpResponseRedirect(reverse('device-edit', args=[device.uid]))
240 return HttpResponseRedirect(reverse('device', args=[device.uid]))
243 @device_decorator
244 @login_required
245 def opml(request, device):
246 response = simple.format_podcast_list(simple.get_subscriptions(request.user, device.uid), 'opml', request.user.username)
247 response['Content-Disposition'] = 'attachment; filename=%s.opml' % device.uid
248 return response
251 @device_decorator
252 @login_required
253 def symbian_opml(request, device):
254 subscriptions = simple.get_subscriptions(request.user, device.uid)
255 subscriptions = map(symbian_opml_changes, subscriptions)
257 response = simple.format_podcast_list(subscriptions, 'opml', request.user.username)
258 response['Content-Disposition'] = 'attachment; filename=%s.opml' % device.uid
259 return response
262 @device_decorator
263 @login_required
264 @allowed_methods(['POST'])
265 def delete(request, device):
266 user = request.user
267 unsync_device(user, device)
268 set_device_deleted(user, device, True)
269 set_device_task_state.delay(user)
270 return HttpResponseRedirect(reverse('devices'))
273 @login_required
274 @device_decorator
275 def delete_permanently(request, device):
277 states = podcast_states_for_device(device.id)
278 for state in states:
279 remove_device_from_podcast_state(state, device)
281 @repeat_on_conflict(['user'])
282 def _remove(user, device):
283 user.remove_device(device)
284 user.save()
286 _remove(user=request.user, device=device)
288 return HttpResponseRedirect(reverse('devices'))
290 @device_decorator
291 @login_required
292 def undelete(request, device):
293 user = request.user
294 set_device_deleted(user, device, False)
295 set_device_task_state.delay(user)
296 return HttpResponseRedirect(reverse('device', args=[device.uid]))
299 @device_decorator
300 @login_required
301 @allowed_methods(['POST'])
302 def sync(request, device):
304 form = SyncForm(request.POST)
305 if not form.is_valid():
306 return HttpResponseBadRequest('invalid')
309 @repeat_on_conflict(['user'])
310 def do_sync(user, device, sync_target):
311 user.sync_devices(device, sync_target)
312 user.save()
315 try:
316 target_uid = form.get_target()
317 sync_target = request.user.get_device_by_uid(target_uid)
318 do_sync(user=request.user, device=device, sync_target=sync_target)
320 except DeviceDoesNotExist as e:
321 messages.error(request, str(e))
323 sync_user.delay(request.user)
325 return HttpResponseRedirect(reverse('device', args=[device.uid]))
328 @device_decorator
329 @login_required
330 @allowed_methods(['GET'])
331 def unsync(request, device):
333 @repeat_on_conflict(['user'])
334 def do_unsync(user, device):
335 user.unsync_device(device)
336 user.save()
338 try:
339 do_unsync(user=request.user, device=device)
341 except ValueError, e:
342 messages.error(request, 'Could not unsync the device: {err}'.format(
343 err=str(e)))
345 return HttpResponseRedirect(reverse('device', args=[device.uid]))
348 from mygpo.web import views
349 history = views.history