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
, 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
46 @cache_control(private
=True)
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
):
63 @cache_control(private
=True)
65 def _decorator(request
, uid
, *args
, **kwargs
):
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
)
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', {
95 'sync_form': sync_form
,
96 'subscriptions': subscriptions
,
97 'synced_with': synced_with
,
98 'has_sync_targets': len(sync_targets
) > 0,
104 @allowed_methods(['POST'])
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'))
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(' ', '-')
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', {
128 'device_form': device_form
,
132 messages
.error(request
, _("You can't use the same Device "
133 "ID for two devices."))
135 return render(request
, 'device-create.html', {
137 'device_form': device_form
,
141 return HttpResponseRedirect(reverse('device-edit', args
=[device
.uid
]))
147 @allowed_methods(['POST'])
148 def update(request
, device
):
149 device_form
= DeviceForm(request
.POST
)
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(' ', '-')
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
]))
175 @cache_control(private
=True)
176 @allowed_methods(['GET'])
177 def edit_new(request
):
181 device_form
= DeviceForm({
187 return render(request
, 'device-create.html', {
189 'device_form': device_form
,
197 @allowed_methods(['GET'])
198 def edit(request
, device
):
200 device_form
= DeviceForm({
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', {
215 'device_form': device_form
,
216 'sync_form': sync_form
,
217 'synced_with': synced_with
,
218 'has_sync_targets': len(sync_targets
) > 0,
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()
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
]))
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
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
264 @allowed_methods(['POST'])
265 def delete(request
, device
):
267 unsync_device(user
, device
)
268 set_device_deleted(user
, device
, True)
269 set_device_task_state
.delay(user
)
270 return HttpResponseRedirect(reverse('devices'))
275 def delete_permanently(request
, device
):
277 states
= podcast_states_for_device(device
.id)
279 remove_device_from_podcast_state(state
, device
)
281 @repeat_on_conflict(['user'])
282 def _remove(user
, device
):
283 user
.remove_device(device
)
286 _remove(user
=request
.user
, device
=device
)
288 return HttpResponseRedirect(reverse('devices'))
292 def undelete(request
, device
):
294 set_device_deleted(user
, device
, False)
295 set_device_task_state
.delay(user
)
296 return HttpResponseRedirect(reverse('device', args
=[device
.uid
]))
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
)
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
]))
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
)
339 do_unsync(user
=request
.user
, device
=device
)
341 except ValueError, e
:
342 messages
.error(request
, 'Could not unsync the device: {err}'.format(
345 return HttpResponseRedirect(reverse('device', args
=[device
.uid
]))
348 from mygpo
.web
import views
349 history
= views
.history