[Web] catch correct excepion when Client update fails
[mygpo.git] / mygpo / web / views / device.py
blobac7e0b5c8f044cd33dbb61dc62f756ae889c2edf
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 import uuid
19 from functools import wraps
20 from xml.parsers.expat import ExpatError
22 from django.db import transaction, IntegrityError
23 from django.shortcuts import render
24 from django.core.urlresolvers import reverse
25 from django.core.exceptions import ValidationError
26 from django.http import HttpResponseRedirect, HttpResponseBadRequest, \
27 HttpResponseNotFound
28 from django.contrib import messages
29 from mygpo.web.forms import DeviceForm, SyncForm
30 from mygpo.web.utils import symbian_opml_changes
31 from django.utils.translation import ugettext as _
32 from django.contrib.auth.decorators import login_required
33 from django.views.decorators.vary import vary_on_cookie
34 from django.views.decorators.cache import never_cache, cache_control
36 from mygpo.api import simple
37 from mygpo.decorators import allowed_methods, repeat_on_conflict
38 from mygpo.users.models import Client, UserProxy
39 from mygpo.subscriptions.models import Subscription
40 from mygpo.users.tasks import sync_user
43 @vary_on_cookie
44 @cache_control(private=True)
45 @login_required
46 def overview(request):
48 user = UserProxy.objects.from_user(request.user)
49 device_groups = user.get_grouped_devices()
50 deleted_devices = Client.objects.filter(user=request.user, deleted=True)
52 # create a "default" device
53 device = Client()
54 device_form = DeviceForm({
55 'name': device.name,
56 'type': device.type,
57 'uid': device.uid
60 return render(request, 'devicelist.html', {
61 'device_groups': list(device_groups),
62 'deleted_devices': list(deleted_devices),
63 'device_form': device_form,
68 def device_decorator(f):
69 @login_required
70 @vary_on_cookie
71 @cache_control(private=True)
72 @wraps(f)
73 def _decorator(request, uid, *args, **kwargs):
75 try:
76 device = Client.objects.get(user=request.user, uid=uid)
78 except Client.DoesNotExist as e:
79 return HttpResponseNotFound(str(e))
81 return f(request, device, *args, **kwargs)
83 return _decorator
87 @login_required
88 @device_decorator
89 def show(request, device):
91 subscriptions = list(device.get_subscribed_podcasts())
92 synced_with = device.synced_with()
94 sync_targets = list(device.get_sync_targets())
95 sync_form = SyncForm()
96 sync_form.set_targets(sync_targets,
97 _('Synchronize with the following devices'))
99 return render(request, 'device.html', {
100 'device': device,
101 'sync_form': sync_form,
102 'subscriptions': subscriptions,
103 'synced_with': synced_with,
104 'has_sync_targets': len(sync_targets) > 0,
108 @login_required
109 @never_cache
110 @allowed_methods(['POST'])
111 def create(request):
112 device_form = DeviceForm(request.POST)
114 if not device_form.is_valid():
115 messages.error(request, _('Please fill out all fields.'))
116 return HttpResponseRedirect(reverse('devices'))
118 try:
119 device = Client()
120 device.user = request.user
121 device.id = uuid.uuid1()
122 device.name = device_form.cleaned_data['name']
123 device.type = device_form.cleaned_data['type']
124 device.uid = device_form.cleaned_data['uid'].replace(' ', '-')
125 device.save()
126 messages.success(request, _('Device saved'))
128 except ValidationError as e:
129 messages.error(request, _(unicode(e)))
130 return HttpResponseRedirect(reverse('devices'))
132 except IntegrityError:
133 messages.error(request, _("You can't use the same Device "
134 "ID for two devices."))
135 return HttpResponseRedirect(reverse('devices'))
137 return HttpResponseRedirect(reverse('device-edit', args=[device.uid]))
141 @device_decorator
142 @login_required
143 @allowed_methods(['POST'])
144 def update(request, device):
145 device_form = DeviceForm(request.POST)
147 uid = device.uid
149 if device_form.is_valid():
151 try:
152 device.name = device_form.cleaned_data['name']
153 device.type = device_form.cleaned_data['type']
154 device.uid = device_form.cleaned_data['uid'].replace(' ', '-')
155 device.save()
156 messages.success(request, _('Device updated'))
157 uid = device.uid # accept the new UID after rest has succeeded
159 except ValidationError as e:
160 messages.error(request, _(str(e)))
162 except IntegrityError:
163 messages.error(request, _("You can't use the same Device "
164 "ID for two devices."))
166 return HttpResponseRedirect(reverse('device-edit', args=[uid]))
169 @device_decorator
170 @login_required
171 @allowed_methods(['GET'])
172 def edit(request, device):
174 device_form = DeviceForm({
175 'name': device.name,
176 'type': device.type,
177 'uid': device.uid
180 synced_with = device.synced_with()
182 sync_targets = list(device.get_sync_targets())
183 sync_form = SyncForm()
184 sync_form.set_targets(sync_targets,
185 _('Synchronize with the following devices'))
187 return render(request, 'device-edit.html', {
188 'device': device,
189 'device_form': device_form,
190 'sync_form': sync_form,
191 'synced_with': synced_with,
192 'has_sync_targets': len(sync_targets) > 0,
196 @device_decorator
197 @login_required
198 def upload_opml(request, device):
200 if not 'opml' in request.FILES:
201 return HttpResponseRedirect(reverse('device-edit', args=[device.uid]))
203 opml = request.FILES['opml'].read()
205 try:
206 subscriptions = simple.parse_subscription(opml, 'opml')
207 simple.set_subscriptions(subscriptions, request.user, device.uid, None)
209 except ExpatError as ee:
210 msg = _('Could not upload subscriptions: {err}').format(err=str(ee))
211 messages.error(request, msg)
212 return HttpResponseRedirect(reverse('device-edit', args=[device.uid]))
214 return HttpResponseRedirect(reverse('device', args=[device.uid]))
217 @device_decorator
218 @login_required
219 def opml(request, device):
220 response = simple.format_podcast_list(simple.get_subscriptions(request.user, device.uid), 'opml', request.user.username)
221 response['Content-Disposition'] = 'attachment; filename=%s.opml' % device.uid
222 return response
225 @device_decorator
226 @login_required
227 def symbian_opml(request, device):
228 subscriptions = simple.get_subscriptions(request.user, device.uid)
229 subscriptions = map(symbian_opml_changes, subscriptions)
231 response = simple.format_podcast_list(subscriptions, 'opml', request.user.username)
232 response['Content-Disposition'] = 'attachment; filename=%s.opml' % device.uid
233 return response
236 @device_decorator
237 @login_required
238 @allowed_methods(['POST'])
239 @transaction.atomic
240 def delete(request, device):
241 """ Mars a client as deleted, but does not permanently delete it """
243 # remoe the device from the sync group
244 device.stop_sync()
246 # mark the subscriptions as deleted
247 Subscription.objects.filter(user=request.user, client=device)\
248 .update(deleted=True)
250 # mark the client as deleted
251 device.deleted = True
252 device.save()
254 return HttpResponseRedirect(reverse('devices'))
257 @login_required
258 @device_decorator
259 def delete_permanently(request, device):
260 device.delete()
261 return HttpResponseRedirect(reverse('devices'))
263 @device_decorator
264 @login_required
265 @transaction.atomic
266 def undelete(request, device):
267 """ Marks the client as not deleted anymore """
269 # mark the subscriptions as not deleted anymore
270 Subscription.objects.filter(user=request.user, client=device)\
271 .update(deleted=False)
273 # mark the client as not deleted anymore
274 device.deleted = False
275 device.save()
277 return HttpResponseRedirect(reverse('device', args=[device.uid]))
280 @device_decorator
281 @login_required
282 @allowed_methods(['POST'])
283 def sync(request, device):
285 form = SyncForm(request.POST)
286 if not form.is_valid():
287 return HttpResponseBadRequest('invalid')
289 try:
290 target_uid = form.get_target()
291 sync_target = request.user.client_set.get(uid=target_uid)
292 device.sync_with(sync_target)
294 except Client.DoesNotExist as e:
295 messages.error(request, str(e))
297 sync_user.delay(request.user)
299 return HttpResponseRedirect(reverse('device', args=[device.uid]))
302 @device_decorator
303 @login_required
304 @allowed_methods(['GET'])
305 def unsync(request, device):
307 @repeat_on_conflict(['user'])
308 def do_unsync(user, device):
309 device.stop_sync()
310 user.save()
312 try:
313 do_unsync(user=request.user, device=device)
315 except ValueError, e:
316 messages.error(request, 'Could not unsync the device: {err}'.format(
317 err=str(e)))
319 return HttpResponseRedirect(reverse('device', args=[device.uid]))