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/>.
19 from functools
import wraps
20 from xml
.parsers
.expat
import ExpatError
22 from django
.shortcuts
import render
23 from django
.core
.urlresolvers
import reverse
24 from django
.core
.exceptions
import ValidationError
25 from django
.http
import HttpResponseRedirect
, HttpResponseBadRequest
, \
27 from django
.contrib
import messages
28 from mygpo
.web
.forms
import DeviceForm
, SyncForm
29 from mygpo
.web
.utils
import symbian_opml_changes
30 from django
.utils
.translation
import ugettext
as _
31 from django
.contrib
.auth
.decorators
import login_required
32 from django
.views
.decorators
.vary
import vary_on_cookie
33 from django
.views
.decorators
.cache
import never_cache
, cache_control
35 from restkit
.errors
import Unauthorized
37 from mygpo
.api
import simple
38 from mygpo
.decorators
import allowed_methods
, repeat_on_conflict
39 from mygpo
.users
.models
import Client
, UserProxy
40 from mygpo
.users
.tasks
import sync_user
, set_device_task_state
41 from mygpo
.db
.couchdb
.podcast_state
import podcast_states_for_device
, \
42 remove_device_from_podcast_state
46 @cache_control(private
=True)
48 def overview(request
):
50 user
= UserProxy
.objects
.from_user(request
.user
)
51 device_groups
= user
.get_grouped_devices()
52 deleted_devices
= Client
.objects
.filter(user
=request
.user
, deleted
=True)
54 # create a "default" device
56 device_form
= DeviceForm({
62 return render(request
, 'devicelist.html', {
63 'device_groups': list(device_groups
),
64 'deleted_devices': list(deleted_devices
),
65 'device_form': device_form
,
70 def device_decorator(f
):
73 @cache_control(private
=True)
75 def _decorator(request
, uid
, *args
, **kwargs
):
78 device
= Client
.objects
.get(user
=request
.user
, uid
=uid
)
80 except Client
.DoesNotExist
as e
:
81 return HttpResponseNotFound(str(e
))
83 return f(request
, device
, *args
, **kwargs
)
91 def show(request
, device
):
93 subscriptions
= list(device
.get_subscribed_podcasts())
94 synced_with
= device
.synced_with()
96 sync_targets
= list(device
.get_sync_targets())
97 sync_form
= SyncForm()
98 sync_form
.set_targets(sync_targets
,
99 _('Synchronize with the following devices'))
101 return render(request
, 'device.html', {
103 'sync_form': sync_form
,
104 'subscriptions': subscriptions
,
105 'synced_with': synced_with
,
106 'has_sync_targets': len(sync_targets
) > 0,
112 @allowed_methods(['POST'])
114 device_form
= DeviceForm(request
.POST
)
116 if not device_form
.is_valid():
117 messages
.error(request
, _('Please fill out all fields.'))
118 return HttpResponseRedirect(reverse('devices'))
122 device
.user
= request
.user
123 device
.id = uuid
.uuid1()
124 device
.name
= device_form
.cleaned_data
['name']
125 device
.type = device_form
.cleaned_data
['type']
126 device
.uid
= device_form
.cleaned_data
['uid'].replace(' ', '-')
128 messages
.success(request
, _('Device saved'))
130 except ValidationError
as e
:
131 messages
.error(request
, _(unicode(e
)))
132 return HttpResponseRedirect(reverse('devices'))
135 messages
.error(request
, _("You can't use the same Device "
136 "ID for two devices."))
137 return HttpResponseRedirect(reverse('devices'))
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():
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 messages
.success(request
, _('Device updated'))
159 uid
= device
.uid
# accept the new UID after rest has succeeded
161 except ValidationError
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 @allowed_methods(['GET'])
174 def edit(request
, device
):
176 device_form
= DeviceForm({
182 synced_with
= device
.synced_with()
184 sync_targets
= list(device
.get_sync_targets())
185 sync_form
= SyncForm()
186 sync_form
.set_targets(sync_targets
,
187 _('Synchronize with the following devices'))
189 return render(request
, 'device-edit.html', {
191 'device_form': device_form
,
192 'sync_form': sync_form
,
193 'synced_with': synced_with
,
194 'has_sync_targets': len(sync_targets
) > 0,
200 def upload_opml(request
, device
):
202 if not 'opml' in request
.FILES
:
203 return HttpResponseRedirect(reverse('device-edit', args
=[device
.uid
]))
205 opml
= request
.FILES
['opml'].read()
208 subscriptions
= simple
.parse_subscription(opml
, 'opml')
209 simple
.set_subscriptions(subscriptions
, request
.user
, device
.uid
, None)
211 except ExpatError
as ee
:
212 msg
= _('Could not upload subscriptions: {err}').format(err
=str(ee
))
213 messages
.error(request
, msg
)
214 return HttpResponseRedirect(reverse('device-edit', args
=[device
.uid
]))
216 return HttpResponseRedirect(reverse('device', args
=[device
.uid
]))
221 def opml(request
, device
):
222 response
= simple
.format_podcast_list(simple
.get_subscriptions(request
.user
, device
.uid
), 'opml', request
.user
.username
)
223 response
['Content-Disposition'] = 'attachment; filename=%s.opml' % device
.uid
229 def symbian_opml(request
, device
):
230 subscriptions
= simple
.get_subscriptions(request
.user
, device
.uid
)
231 subscriptions
= map(symbian_opml_changes
, subscriptions
)
233 response
= simple
.format_podcast_list(subscriptions
, 'opml', request
.user
.username
)
234 response
['Content-Disposition'] = 'attachment; filename=%s.opml' % device
.uid
240 @allowed_methods(['POST'])
241 def delete(request
, device
):
244 device
.deleted
= True
246 set_device_task_state
.delay(user
)
247 return HttpResponseRedirect(reverse('devices'))
252 def delete_permanently(request
, device
):
254 states
= podcast_states_for_device(device
.id.hex)
256 remove_device_from_podcast_state(state
, device
)
259 return HttpResponseRedirect(reverse('devices'))
263 def undelete(request
, device
):
265 device
.deleted
= False
267 set_device_task_state
.delay(user
)
268 return HttpResponseRedirect(reverse('device', args
=[device
.uid
]))
273 @allowed_methods(['POST'])
274 def sync(request
, device
):
276 form
= SyncForm(request
.POST
)
277 if not form
.is_valid():
278 return HttpResponseBadRequest('invalid')
281 target_uid
= form
.get_target()
282 sync_target
= request
.user
.client_set
.get(uid
=target_uid
)
283 device
.sync_with(sync_target
)
285 except Client
.DoesNotExist
as e
:
286 messages
.error(request
, str(e
))
288 sync_user
.delay(request
.user
)
290 return HttpResponseRedirect(reverse('device', args
=[device
.uid
]))
295 @allowed_methods(['GET'])
296 def unsync(request
, device
):
298 @repeat_on_conflict(['user'])
299 def do_unsync(user
, device
):
304 do_unsync(user
=request
.user
, device
=device
)
306 except ValueError, e
:
307 messages
.error(request
, 'Could not unsync the device: {err}'.format(
310 return HttpResponseRedirect(reverse('device', args
=[device
.uid
]))
313 from mygpo
.web
import views
314 history
= views
.history