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 DeviceUIDException
, Client
38 from mygpo
.users
.tasks
import sync_user
, set_device_task_state
39 from mygpo
.users
.sync
import get_grouped_devices
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
= get_grouped_devices(request
.user
)
51 deleted_devices
= Client
.objects
.filter(user
=request
.user
, deleted
=True)
53 # create a "default" device
55 device_form
= DeviceForm({
61 return render(request
, 'devicelist.html', {
62 'device_groups': device_groups
,
63 'deleted_devices': deleted_devices
,
64 'device_form': device_form
,
69 def device_decorator(f
):
72 @cache_control(private
=True)
74 def _decorator(request
, uid
, *args
, **kwargs
):
77 device
= Client
.objects
.get(user
=request
.user
, uid
=uid
)
79 except Client
.DoesNotExist
as e
:
80 return HttpResponseNotFound(str(e
))
82 return f(request
, device
, *args
, **kwargs
)
90 def show(request
, device
):
92 request
.user
.sync_group(device
)
94 subscriptions
= list(device
.get_subscribed_podcasts())
95 synced_with
= request
.user
.get_synced(device
)
97 sync_targets
= list(request
.user
.get_sync_targets(device
))
98 sync_form
= SyncForm()
99 sync_form
.set_targets(sync_targets
,
100 _('Synchronize with the following devices'))
102 return render(request
, 'device.html', {
104 'sync_form': sync_form
,
105 'subscriptions': subscriptions
,
106 'synced_with': synced_with
,
107 'has_sync_targets': len(sync_targets
) > 0,
113 @allowed_methods(['POST'])
115 device_form
= DeviceForm(request
.POST
)
117 if not device_form
.is_valid():
118 messages
.error(request
, _('Please fill out all fields.'))
119 return HttpResponseRedirect(reverse('devices'))
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(' ', '-')
126 set_device(request
.user
, device
)
127 messages
.success(request
, _('Device saved'))
129 except DeviceUIDException
as e
:
130 messages
.error(request
, _(unicode(e
)))
131 return HttpResponseRedirect(reverse('devices'))
134 messages
.error(request
, _("You can't use the same Device "
135 "ID for two devices."))
136 return HttpResponseRedirect(reverse('devices'))
138 return HttpResponseRedirect(reverse('device-edit', args
=[device
.uid
]))
144 @allowed_methods(['POST'])
145 def update(request
, device
):
146 device_form
= DeviceForm(request
.POST
)
150 if device_form
.is_valid():
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(' ', '-')
156 set_device(request
.user
, device
)
157 messages
.success(request
, _('Device updated'))
158 uid
= device
.uid
# accept the new UID after rest has succeeded
160 except DeviceUIDException
as e
:
161 messages
.error(request
, _(str(e
)))
163 except Unauthorized
as u
:
164 messages
.error(request
, _("You can't use the same Device "
165 "ID for two devices."))
167 return HttpResponseRedirect(reverse('device-edit', args
=[uid
]))
172 @allowed_methods(['GET'])
173 def edit(request
, device
):
175 device_form
= DeviceForm({
181 synced_with
= request
.user
.get_synced(device
)
183 sync_targets
= list(request
.user
.get_sync_targets(device
))
184 sync_form
= SyncForm()
185 sync_form
.set_targets(sync_targets
,
186 _('Synchronize with the following devices'))
188 return render(request
, 'device-edit.html', {
190 'device_form': device_form
,
191 'sync_form': sync_form
,
192 'synced_with': synced_with
,
193 'has_sync_targets': len(sync_targets
) > 0,
199 def upload_opml(request
, device
):
201 if not 'opml' in request
.FILES
:
202 return HttpResponseRedirect(reverse('device-edit', args
=[device
.uid
]))
204 opml
= request
.FILES
['opml'].read()
207 subscriptions
= simple
.parse_subscription(opml
, 'opml')
208 simple
.set_subscriptions(subscriptions
, request
.user
, device
.uid
, None)
210 except ExpatError
as ee
:
211 msg
= _('Could not upload subscriptions: {err}').format(err
=str(ee
))
212 messages
.error(request
, msg
)
213 return HttpResponseRedirect(reverse('device-edit', args
=[device
.uid
]))
215 return HttpResponseRedirect(reverse('device', args
=[device
.uid
]))
220 def opml(request
, device
):
221 response
= simple
.format_podcast_list(simple
.get_subscriptions(request
.user
, device
.uid
), 'opml', request
.user
.username
)
222 response
['Content-Disposition'] = 'attachment; filename=%s.opml' % device
.uid
228 def symbian_opml(request
, device
):
229 subscriptions
= simple
.get_subscriptions(request
.user
, device
.uid
)
230 subscriptions
= map(symbian_opml_changes
, subscriptions
)
232 response
= simple
.format_podcast_list(subscriptions
, 'opml', request
.user
.username
)
233 response
['Content-Disposition'] = 'attachment; filename=%s.opml' % device
.uid
239 @allowed_methods(['POST'])
240 def delete(request
, device
):
242 unsync_device(user
, device
)
243 set_device_deleted(user
, device
, True)
244 set_device_task_state
.delay(user
)
245 return HttpResponseRedirect(reverse('devices'))
250 def delete_permanently(request
, device
):
252 states
= podcast_states_for_device(device
.id)
254 remove_device_from_podcast_state(state
, device
)
256 @repeat_on_conflict(['user'])
257 def _remove(user
, device
):
258 user
.remove_device(device
)
261 _remove(user
=request
.user
, device
=device
)
263 return HttpResponseRedirect(reverse('devices'))
267 def undelete(request
, device
):
269 set_device_deleted(user
, device
, False)
270 set_device_task_state
.delay(user
)
271 return HttpResponseRedirect(reverse('device', args
=[device
.uid
]))
276 @allowed_methods(['POST'])
277 def sync(request
, device
):
279 form
= SyncForm(request
.POST
)
280 if not form
.is_valid():
281 return HttpResponseBadRequest('invalid')
284 target_uid
= form
.get_target()
285 sync_target
= request
.user
.get_device_by_uid(target_uid
)
286 device
.sync_with(sync_target
)
288 except Client
.DoesNotExist
as e
:
289 messages
.error(request
, str(e
))
291 sync_user
.delay(request
.user
)
293 return HttpResponseRedirect(reverse('device', args
=[device
.uid
]))
298 @allowed_methods(['GET'])
299 def unsync(request
, device
):
301 @repeat_on_conflict(['user'])
302 def do_unsync(user
, device
):
303 user
.unsync_device(device
)
307 do_unsync(user
=request
.user
, device
=device
)
309 except ValueError, e
:
310 messages
.error(request
, 'Could not unsync the device: {err}'.format(
313 return HttpResponseRedirect(reverse('device', args
=[device
.uid
]))
316 from mygpo
.web
import views
317 history
= views
.history