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
.db
import transaction
, IntegrityError
23 from django
.shortcuts
import render
24 from django
.core
.urlresolvers
import reverse
25 from django
.core
.exceptions
import ValidationError
26 from django
.http
import HttpResponseRedirect
, HttpResponseBadRequest
, \
28 from django
.contrib
import messages
29 from mygpo
.web
.forms
import DeviceForm
, SyncForm
30 from mygpo
.web
.utils
import symbian_opml_changes
31 from django
.utils
.translation
import ugettext
as _
32 from django
.contrib
.auth
.decorators
import login_required
33 from django
.views
.decorators
.vary
import vary_on_cookie
34 from django
.views
.decorators
.cache
import never_cache
, cache_control
36 from mygpo
.api
import simple
37 from mygpo
.decorators
import allowed_methods
38 from mygpo
.users
.models
import Client
, UserProxy
39 from mygpo
.subscriptions
.models
import Subscription
40 from mygpo
.users
.tasks
import sync_user
44 @cache_control(private
=True)
46 def overview(request
):
48 user
= UserProxy
.objects
.from_user(request
.user
)
49 device_groups
= user
.get_grouped_devices()
50 deleted_devices
= Client
.objects
.filter(user
=request
.user
, deleted
=True)
52 # create a "default" device
54 device_form
= DeviceForm({
60 return render(request
, 'devicelist.html', {
61 'device_groups': list(device_groups
),
62 'deleted_devices': list(deleted_devices
),
63 'device_form': device_form
,
68 def device_decorator(f
):
71 @cache_control(private
=True)
73 def _decorator(request
, uid
, *args
, **kwargs
):
76 device
= Client
.objects
.get(user
=request
.user
, uid
=uid
)
78 except Client
.DoesNotExist
as e
:
79 return HttpResponseNotFound(str(e
))
81 return f(request
, device
, *args
, **kwargs
)
89 def show(request
, device
):
91 subscriptions
= list(device
.get_subscribed_podcasts())
92 synced_with
= device
.synced_with()
94 sync_targets
= list(device
.get_sync_targets())
95 sync_form
= SyncForm()
96 sync_form
.set_targets(sync_targets
,
97 _('Synchronize with the following devices'))
99 return render(request
, 'device.html', {
101 'sync_form': sync_form
,
102 'subscriptions': subscriptions
,
103 'synced_with': synced_with
,
104 'has_sync_targets': len(sync_targets
) > 0,
110 @allowed_methods(['POST'])
112 device_form
= DeviceForm(request
.POST
)
114 if not device_form
.is_valid():
115 messages
.error(request
, _('Please fill out all fields.'))
116 return HttpResponseRedirect(reverse('devices'))
120 device
.user
= request
.user
121 device
.id = uuid
.uuid1()
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(' ', '-')
127 messages
.success(request
, _('Device saved'))
129 except ValidationError
as e
:
130 messages
.error(request
, '; '.join(e
.messages
))
131 return HttpResponseRedirect(reverse('devices'))
133 except IntegrityError
:
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():
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(' ', '-')
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 IntegrityError
:
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
]))
206 opml
= request
.FILES
['opml'].read().decode('utf-8')
207 subscriptions
= simple
.parse_subscription(opml
, 'opml')
208 simple
.set_subscriptions(subscriptions
, request
.user
, device
.uid
, None)
210 except (ValueError, ExpatError
, UnicodeDecodeError) as ex
:
211 msg
= _('Could not upload subscriptions: {err}').format(err
=str(ex
))
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'])
241 def delete(request
, device
):
242 """ Mars a client as deleted, but does not permanently delete it """
244 # remoe the device from the sync group
247 # mark the subscriptions as deleted
248 Subscription
.objects
.filter(user
=request
.user
, client
=device
)\
249 .update(deleted
=True)
251 # mark the client as deleted
252 device
.deleted
= True
255 return HttpResponseRedirect(reverse('devices'))
260 def delete_permanently(request
, device
):
262 return HttpResponseRedirect(reverse('devices'))
267 def undelete(request
, device
):
268 """ Marks the client as not deleted anymore """
270 # mark the subscriptions as not deleted anymore
271 Subscription
.objects
.filter(user
=request
.user
, client
=device
)\
272 .update(deleted
=False)
274 # mark the client as not deleted anymore
275 device
.deleted
= False
278 return HttpResponseRedirect(reverse('device', args
=[device
.uid
]))
283 @allowed_methods(['POST'])
284 def sync(request
, device
):
286 form
= SyncForm(request
.POST
)
287 if not form
.is_valid():
288 return HttpResponseBadRequest('invalid')
291 target_uid
= form
.get_target()
292 sync_target
= request
.user
.client_set
.get(uid
=target_uid
)
293 device
.sync_with(sync_target
)
295 except Client
.DoesNotExist
as e
:
296 messages
.error(request
, str(e
))
298 sync_user
.delay(request
.user
)
300 return HttpResponseRedirect(reverse('device', args
=[device
.uid
]))
305 def resync(request
, device
):
306 """ Manually triggers a re-sync of a client """
307 sync_user
.delay(request
.user
)
308 messages
.success(request
,
309 _('Your subscription will be updated in a moment.'))
310 return HttpResponseRedirect(reverse('device', args
=[device
.uid
]))
315 @allowed_methods(['GET'])
316 def unsync(request
, device
):
320 except ValueError as e
:
321 messages
.error(request
, 'Could not unsync the device: {err}'.format(
324 return HttpResponseRedirect(reverse('device', args
=[device
.uid
]))