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
, \
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
, \
39 from mygpo
.users
.tasks
import sync_user
40 from mygpo
.db
.couchdb
.podcast_state
import podcast_states_for_device
44 @cache_control(private
=True)
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
):
61 @cache_control(private
=True)
63 def _decorator(request
, uid
, *args
, **kwargs
):
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
)
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', {
93 'sync_form': sync_form
,
94 'subscriptions': subscriptions
,
95 'synced_with': synced_with
,
96 'has_sync_targets': len(sync_targets
) > 0,
102 @allowed_methods(['POST'])
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'))
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(' ', '-')
118 request
.user
.set_device(device
)
120 messages
.success(request
, _('Device saved'))
122 except DeviceUIDException
as e
:
123 messages
.error(request
, _(unicode(e
)))
125 return render(request
, 'device-create.html', {
127 'device_form': device_form
,
131 messages
.error(request
, _("You can't use the same Device "
132 "ID for two devices."))
134 return render(request
, 'device-create.html', {
136 'device_form': device_form
,
140 return HttpResponseRedirect(reverse('device-edit', args
=[device
.uid
]))
146 @allowed_methods(['POST'])
147 def update(request
, device
):
148 device_form
= DeviceForm(request
.POST
)
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(' ', '-')
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
]))
174 @cache_control(private
=True)
175 @allowed_methods(['GET'])
176 def edit_new(request
):
180 device_form
= DeviceForm({
186 return render(request
, 'device-create.html', {
188 'device_form': device_form
,
196 @allowed_methods(['GET'])
197 def edit(request
, device
):
199 device_form
= DeviceForm({
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', {
214 'device_form': device_form
,
215 'sync_form': sync_form
,
216 'synced_with': synced_with
,
217 'has_sync_targets': len(sync_targets
) > 0,
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()
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
]))
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
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
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
)
276 _delete(user
=request
.user
, device
=device
)
278 return HttpResponseRedirect(reverse('devices'))
283 def delete_permanently(request
, device
):
285 @repeat_on_conflict(['state'])
286 def remove_device(state
, dev
):
287 state
.remove_device(dev
)
290 states
= podcast_states_for_device(device
.id)
292 remove_device(state
=state
, dev
=device
)
294 @repeat_on_conflict(['user'])
295 def _remove(user
, device
):
296 user
.remove_device(device
)
299 _remove(user
=request
.user
, device
=device
)
301 return HttpResponseRedirect(reverse('devices'))
305 def undelete(request
, device
):
307 device
.deleted
= False
308 request
.user
.update_device(device
)
310 return HttpResponseRedirect(reverse('device', args
=[device
.uid
]))
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
)
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
]))
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
)
353 do_unsync(user
=request
.user
, device
=device
)
355 except ValueError, e
:
356 messages
.error(request
, 'Could not unsync the device: {err}'.format(
359 return HttpResponseRedirect(reverse('device', args
=[device
.uid
]))
362 from mygpo
.web
import views
363 history
= views
.history