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
.db
.couchdb
.podcast_state
import podcast_states_for_device
43 @cache_control(private
=True)
45 def overview(request
):
47 device_groups
= request
.user
.get_grouped_devices()
48 deleted_devices
= request
.user
.inactive_devices
50 return render(request
, 'devicelist.html', {
51 'device_groups': device_groups
,
52 'deleted_devices': deleted_devices
,
57 def device_decorator(f
):
60 @cache_control(private
=True)
62 def _decorator(request
, uid
, *args
, **kwargs
):
65 device
= request
.user
.get_device_by_uid(uid
, only_active
=False)
67 except DeviceDoesNotExist
as e
:
68 return HttpResponseNotFound(str(e
))
70 return f(request
, device
, *args
, **kwargs
)
78 def show(request
, device
):
80 request
.user
.sync_group(device
)
82 subscriptions
= list(device
.get_subscribed_podcasts())
83 synced_with
= request
.user
.get_synced(device
)
85 sync_targets
= list(request
.user
.get_sync_targets(device
))
86 sync_form
= SyncForm()
87 sync_form
.set_targets(sync_targets
,
88 _('Synchronize with the following devices'))
90 return render(request
, 'device.html', {
92 'sync_form': sync_form
,
93 'subscriptions': subscriptions
,
94 'synced_with': synced_with
,
95 'has_sync_targets': len(sync_targets
) > 0,
101 @allowed_methods(['POST'])
103 device_form
= DeviceForm(request
.POST
)
105 if not device_form
.is_valid():
107 messages
.error(request
, _('Please fill out all fields.'))
109 return HttpResponseRedirect(reverse('device-edit-new'))
113 device
.name
= device_form
.cleaned_data
['name']
114 device
.type = device_form
.cleaned_data
['type']
115 device
.uid
= device_form
.cleaned_data
['uid'].replace(' ', '-')
117 request
.user
.set_device(device
)
119 messages
.success(request
, _('Device saved'))
121 except DeviceUIDException
as e
:
122 messages
.error(request
, _(str(e
)))
124 return render(request
, 'device-create.html', {
126 'device_form': device_form
,
130 messages
.error(request
, _("You can't use the same Device "
131 "ID for two devices."))
133 return render(request
, 'device-create.html', {
135 'device_form': device_form
,
139 return HttpResponseRedirect(reverse('device-edit', args
=[device
.uid
]))
145 @allowed_methods(['POST'])
146 def update(request
, device
):
147 device_form
= DeviceForm(request
.POST
)
151 if device_form
.is_valid():
153 device
.name
= device_form
.cleaned_data
['name']
154 device
.type = device_form
.cleaned_data
['type']
155 device
.uid
= device_form
.cleaned_data
['uid'].replace(' ', '-')
157 request
.user
.update_device(device
)
158 messages
.success(request
, _('Device updated'))
159 uid
= device
.uid
# accept the new UID after rest has succeeded
161 except DeviceUIDException
as e
:
162 messages
.error(request
, _(str(e
)))
164 except Unauthorized
as u
:
165 messages
.error(request
, _("You can't use the same Device "
166 "ID for two devices."))
168 return HttpResponseRedirect(reverse('device-edit', args
=[uid
]))
173 @cache_control(private
=True)
174 @allowed_methods(['GET'])
175 def edit_new(request
):
179 device_form
= DeviceForm({
185 return render(request
, 'device-create.html', {
187 'device_form': device_form
,
195 @allowed_methods(['GET'])
196 def edit(request
, device
):
198 device_form
= DeviceForm({
204 synced_with
= request
.user
.get_synced(device
)
206 sync_targets
= list(request
.user
.get_sync_targets(device
))
207 sync_form
= SyncForm()
208 sync_form
.set_targets(sync_targets
,
209 _('Synchronize with the following devices'))
211 return render(request
, 'device-edit.html', {
213 'device_form': device_form
,
214 'sync_form': sync_form
,
215 'synced_with': synced_with
,
216 'has_sync_targets': len(sync_targets
) > 0,
222 def upload_opml(request
, device
):
224 if not 'opml' in request
.FILES
:
225 return HttpResponseRedirect(reverse('device-edit', args
=[device
.uid
]))
227 opml
= request
.FILES
['opml'].read()
230 subscriptions
= simple
.parse_subscription(opml
, 'opml')
231 simple
.set_subscriptions(subscriptions
, request
.user
, device
.uid
, None)
233 except ExpatError
as ee
:
234 msg
= _('Could not upload subscriptions: {err}').format(err
=str(ee
))
235 messages
.error(request
, msg
)
236 return HttpResponseRedirect(reverse('device-edit', args
=[device
.uid
]))
238 return HttpResponseRedirect(reverse('device', args
=[device
.uid
]))
243 def opml(request
, device
):
244 response
= simple
.format_podcast_list(simple
.get_subscriptions(request
.user
, device
.uid
), 'opml', request
.user
.username
)
245 response
['Content-Disposition'] = 'attachment; filename=%s.opml' % device
.uid
251 def symbian_opml(request
, device
):
252 subscriptions
= simple
.get_subscriptions(request
.user
, device
.uid
)
253 subscriptions
= map(symbian_opml_changes
, subscriptions
)
255 response
= simple
.format_podcast_list(subscriptions
, 'opml', request
.user
.username
)
256 response
['Content-Disposition'] = 'attachment; filename=%s.opml' % device
.uid
262 @allowed_methods(['POST'])
263 def delete(request
, device
):
265 @repeat_on_conflict(['user'])
266 def _delete(user
, device
):
267 if request
.user
.is_synced(device
):
268 request
.user
.unsync_device(device
)
269 device
.deleted
= True
270 user
.set_device(device
)
271 if user
.is_synced(device
):
272 user
.unsync_device(device
)
275 _delete(user
=request
.user
, device
=device
)
277 return HttpResponseRedirect(reverse('devices'))
282 def delete_permanently(request
, device
):
284 @repeat_on_conflict(['state'])
285 def remove_device(state
, dev
):
286 state
.remove_device(dev
)
289 states
= podcast_states_for_device(device
.id)
291 remove_device(state
=state
, dev
=device
)
293 @repeat_on_conflict(['user'])
294 def _remove(user
, device
):
295 user
.remove_device(device
)
298 _remove(user
=request
.user
, device
=device
)
300 return HttpResponseRedirect(reverse('devices'))
304 def undelete(request
, device
):
306 device
.deleted
= False
307 request
.user
.update_device(device
)
309 return HttpResponseRedirect(reverse('device', args
=[device
.uid
]))
314 @allowed_methods(['POST'])
315 def sync(request
, device
):
317 form
= SyncForm(request
.POST
)
318 if not form
.is_valid():
319 return HttpResponseBadRequest('invalid')
322 @repeat_on_conflict(['user'])
323 def do_sync(user
, device
, sync_target
):
324 user
.sync_devices(device
, sync_target
)
329 target_uid
= form
.get_target()
330 sync_target
= request
.user
.get_device_by_uid(target_uid
)
331 do_sync(user
=request
.user
, device
=device
, sync_target
=sync_target
)
333 except DeviceDoesNotExist
as e
:
334 messages
.error(request
, str(e
))
336 request
.user
.sync_all()
338 return HttpResponseRedirect(reverse('device', args
=[device
.uid
]))
343 @allowed_methods(['GET'])
344 def unsync(request
, device
):
346 @repeat_on_conflict(['user'])
347 def do_unsync(user
, device
):
348 user
.unsync_device(device
)
352 do_unsync(user
=request
.user
, device
=device
)
354 except ValueError, e
:
355 messages
.error(request
, 'Could not unsync the device: {err}'.format(
358 return HttpResponseRedirect(reverse('device', args
=[device
.uid
]))
361 from mygpo
.web
import views
362 history
= views
.history