Merge pull request #793 from gpodder/remove-advertise
[mygpo.git] / mygpo / users / views / device.py
blob89c8aae99742f590be5e76f88ec4b82279549b45
1 import uuid
2 from functools import wraps
3 from xml.parsers.expat import ExpatError
5 from django.db import transaction, IntegrityError
6 from django.shortcuts import render
7 from django.urls import reverse
8 from django.core.exceptions import ValidationError
9 from django.http import (
10 HttpResponseRedirect,
11 HttpResponseBadRequest,
12 HttpResponseNotFound,
14 from django.contrib import messages
15 from mygpo.web.forms import DeviceForm, SyncForm
16 from mygpo.web.utils import symbian_opml_changes
17 from django.utils.translation import gettext as _
18 from django.contrib.auth.decorators import login_required
19 from django.views.decorators.vary import vary_on_cookie
20 from django.views.decorators.cache import never_cache, cache_control
22 from mygpo.api import simple
23 from mygpo.decorators import allowed_methods
24 from mygpo.users.models import Client, UserProxy
25 from mygpo.subscriptions.models import Subscription
26 from mygpo.users.tasks import sync_user
29 @vary_on_cookie
30 @cache_control(private=True)
31 @login_required
32 def overview(request):
34 user = UserProxy.objects.from_user(request.user)
35 device_groups = user.get_grouped_devices()
36 deleted_devices = Client.objects.filter(user=request.user, deleted=True)
38 # create a "default" device
39 device = Client()
40 device_form = DeviceForm(
41 {"name": device.name, "type": device.type, "uid": device.uid}
44 return render(
45 request,
46 "devicelist.html",
48 "device_groups": list(device_groups),
49 "deleted_devices": list(deleted_devices),
50 "device_form": device_form,
55 def device_decorator(f):
56 @login_required
57 @vary_on_cookie
58 @cache_control(private=True)
59 @wraps(f)
60 def _decorator(request, uid, *args, **kwargs):
62 try:
63 device = Client.objects.get(user=request.user, uid=uid)
65 except Client.DoesNotExist as e:
66 return HttpResponseNotFound(str(e))
68 return f(request, device, *args, **kwargs)
70 return _decorator
73 @login_required
74 @device_decorator
75 def show(request, device):
77 subscriptions = list(device.get_subscribed_podcasts().prefetch_related("slugs"))
78 synced_with = device.synced_with()
80 sync_targets = list(device.get_sync_targets())
81 sync_form = SyncForm()
82 sync_form.set_targets(sync_targets, _("Synchronize with the following devices"))
84 return render(
85 request,
86 "device.html",
88 "device": device,
89 "sync_form": sync_form,
90 "subscriptions": subscriptions,
91 "synced_with": synced_with,
92 "has_sync_targets": len(sync_targets) > 0,
97 @login_required
98 @never_cache
99 @allowed_methods(["POST"])
100 def create(request):
101 device_form = DeviceForm(request.POST)
103 if not device_form.is_valid():
104 messages.error(request, _("Please fill out all fields."))
105 return HttpResponseRedirect(reverse("devices"))
107 try:
108 device = Client()
109 device.user = request.user
110 device.id = uuid.uuid1()
111 device.name = device_form.cleaned_data["name"]
112 device.type = device_form.cleaned_data["type"]
113 device.uid = device_form.cleaned_data["uid"].replace(" ", "-")
114 device.full_clean()
115 device.save()
116 messages.success(request, _("Device saved"))
118 except ValidationError as e:
119 messages.error(request, "; ".join(e.messages))
120 return HttpResponseRedirect(reverse("devices"))
122 except IntegrityError:
123 messages.error(
124 request, _("You can't use the same Device " "ID for two devices.")
126 return HttpResponseRedirect(reverse("devices"))
128 return HttpResponseRedirect(reverse("device-edit", args=[device.uid]))
131 @device_decorator
132 @login_required
133 @allowed_methods(["POST"])
134 def update(request, device):
135 device_form = DeviceForm(request.POST)
137 uid = device.uid
139 if device_form.is_valid():
141 try:
142 device.name = device_form.cleaned_data["name"]
143 device.type = device_form.cleaned_data["type"]
144 device.uid = device_form.cleaned_data["uid"].replace(" ", "-")
145 device.full_clean()
146 device.save()
147 messages.success(request, _("Device updated"))
148 uid = device.uid # accept the new UID after rest has succeeded
150 except ValidationError as e:
151 messages.error(request, _(str(e)))
153 except IntegrityError:
154 messages.error(
155 request, _("You can't use the same Device " "ID for two devices.")
158 return HttpResponseRedirect(reverse("device-edit", args=[uid]))
161 @device_decorator
162 @login_required
163 @allowed_methods(["GET"])
164 def edit(request, device):
166 device_form = DeviceForm(
167 {"name": device.name, "type": device.type, "uid": device.uid}
170 synced_with = device.synced_with()
172 sync_targets = list(device.get_sync_targets())
173 sync_form = SyncForm()
174 sync_form.set_targets(sync_targets, _("Synchronize with the following devices"))
176 return render(
177 request,
178 "device-edit.html",
180 "device": device,
181 "device_form": device_form,
182 "sync_form": sync_form,
183 "synced_with": synced_with,
184 "has_sync_targets": len(sync_targets) > 0,
189 @device_decorator
190 @login_required
191 def upload_opml(request, device):
193 if not "opml" in request.FILES:
194 return HttpResponseRedirect(reverse("device-edit", args=[device.uid]))
196 try:
197 opml = request.FILES["opml"].read().decode("utf-8")
198 subscriptions = simple.parse_subscription(opml, "opml")
199 simple.set_subscriptions(subscriptions, request.user, device.uid, None)
201 except (ValueError, ExpatError, UnicodeDecodeError) as ex:
202 msg = _("Could not upload subscriptions: {err}").format(err=str(ex))
203 messages.error(request, msg)
204 return HttpResponseRedirect(reverse("device-edit", args=[device.uid]))
206 return HttpResponseRedirect(reverse("device", args=[device.uid]))
209 @device_decorator
210 @login_required
211 def opml(request, device):
212 response = simple.format_podcast_list(
213 simple.get_subscriptions(request.user, device.uid),
214 "opml",
215 request.user.username,
217 response["Content-Disposition"] = "attachment; filename=%s.opml" % device.uid
218 return response
221 @device_decorator
222 @login_required
223 def symbian_opml(request, device):
224 subscriptions = simple.get_subscriptions(request.user, device.uid)
225 subscriptions = map(symbian_opml_changes, subscriptions)
227 response = simple.format_podcast_list(subscriptions, "opml", request.user.username)
228 response["Content-Disposition"] = "attachment; filename=%s.opml" % device.uid
229 return response
232 @device_decorator
233 @login_required
234 @allowed_methods(["POST"])
235 @transaction.atomic
236 def delete(request, device):
237 """Mars a client as deleted, but does not permanently delete it"""
239 # remoe the device from the sync group
240 device.stop_sync()
242 # mark the subscriptions as deleted
243 Subscription.objects.filter(user=request.user, client=device).update(deleted=True)
245 # mark the client as deleted
246 device.deleted = True
247 device.save()
249 return HttpResponseRedirect(reverse("devices"))
252 @login_required
253 @device_decorator
254 def delete_permanently(request, device):
255 device.delete()
256 return HttpResponseRedirect(reverse("devices"))
259 @device_decorator
260 @login_required
261 @transaction.atomic
262 def undelete(request, device):
263 """Marks the client as not deleted anymore"""
265 # mark the subscriptions as not deleted anymore
266 Subscription.objects.filter(user=request.user, client=device).update(deleted=False)
268 # mark the client as not deleted anymore
269 device.deleted = False
270 device.save()
272 return HttpResponseRedirect(reverse("device", args=[device.uid]))
275 @device_decorator
276 @login_required
277 @allowed_methods(["POST"])
278 def sync(request, device):
280 form = SyncForm(request.POST)
281 if not form.is_valid():
282 return HttpResponseBadRequest("invalid")
284 try:
285 target_uid = form.get_target()
286 sync_target = request.user.client_set.get(uid=target_uid)
287 device.sync_with(sync_target)
289 except Client.DoesNotExist as e:
290 messages.error(request, str(e))
292 sync_user.delay(request.user.pk)
294 return HttpResponseRedirect(reverse("device", args=[device.uid]))
297 @device_decorator
298 @login_required
299 def resync(request, device):
300 """Manually triggers a re-sync of a client"""
301 sync_user.delay(request.user.pk)
302 messages.success(request, _("Your subscription will be updated in a moment."))
303 return HttpResponseRedirect(reverse("device", args=[device.uid]))
306 @device_decorator
307 @login_required
308 @allowed_methods(["GET"])
309 def unsync(request, device):
310 try:
311 device.stop_sync()
313 except ValueError as e:
314 messages.error(request, "Could not unsync the device: {err}".format(err=str(e)))
316 return HttpResponseRedirect(reverse("device", args=[device.uid]))