views.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. # Copyright 2013-2015 The Distro Tracker Developers
  2. # See the COPYRIGHT file at the top-level directory of this distribution and
  3. # at http://deb.li/DTAuthors
  4. #
  5. # This file is part of Distro Tracker. It is subject to the license terms
  6. # in the LICENSE file found in the top-level directory of this
  7. # distribution and at http://deb.li/DTLicense. No part of Distro Tracker,
  8. # including this file, may be copied, modified, propagated, or distributed
  9. # except according to the terms contained in the LICENSE file.
  10. """Views for the :mod:`distro_tracker.accounts` app."""
  11. from __future__ import unicode_literals
  12. from django.views.generic.base import View
  13. from django.core.urlresolvers import reverse_lazy
  14. from django.shortcuts import get_object_or_404
  15. from django.shortcuts import render
  16. from django.shortcuts import redirect
  17. from django.http import HttpResponseBadRequest
  18. from django.http import HttpResponseForbidden
  19. from django.http import Http404
  20. from django.conf import settings
  21. from django.core.exceptions import ValidationError
  22. from distro_tracker.accounts.models import UserEmail
  23. from distro_tracker.core.utils import distro_tracker_render_to_string
  24. from distro_tracker.core.utils import render_to_json_response
  25. from distro_tracker.core.models import Subscription
  26. from distro_tracker.core.models import EmailSettings
  27. from distro_tracker.core.models import Keyword
  28. from django_email_accounts import views as email_accounts_views
  29. from django_email_accounts.views import LoginRequiredMixin
  30. class ConfirmationRenderMixin(object):
  31. def get_confirmation_email_content(self, confirmation):
  32. return distro_tracker_render_to_string(
  33. self.confirmation_email_template,
  34. {'confirmation': confirmation}
  35. )
  36. class LoginView(email_accounts_views.LoginView):
  37. success_url = reverse_lazy('dtracker-accounts-profile')
  38. class LogoutView(email_accounts_views.LogoutView):
  39. success_url = reverse_lazy('dtracker-index')
  40. class RegisterUser(ConfirmationRenderMixin, email_accounts_views.RegisterUser):
  41. success_url = reverse_lazy('dtracker-accounts-register-success')
  42. confirmation_email_subject = '{name} Registration Confirmation'.format(
  43. name=settings.GET_INSTANCE_NAME())
  44. confirmation_email_from_address = settings.DISTRO_TRACKER_CONTACT_EMAIL
  45. class RegistrationConfirmation(email_accounts_views.RegistrationConfirmation):
  46. success_url = reverse_lazy('dtracker-accounts-profile')
  47. message = 'You have successfully registered to the {name}'.format(
  48. name=settings.GET_INSTANCE_NAME())
  49. class ResetPasswordView(ConfirmationRenderMixin,
  50. email_accounts_views.ResetPasswordView):
  51. success_url = reverse_lazy('dtracker-accounts-profile')
  52. class ForgotPasswordView(ConfirmationRenderMixin,
  53. email_accounts_views.ForgotPasswordView):
  54. success_url = reverse_lazy('dtracker-accounts-password-reset-success')
  55. email_subject = '{name} Password Reset Confirmation'.format(
  56. name=settings.GET_INSTANCE_NAME())
  57. email_from_address = settings.DISTRO_TRACKER_CONTACT_EMAIL
  58. class ChangePersonalInfoView(email_accounts_views.ChangePersonalInfoView):
  59. success_url = reverse_lazy('dtracker-accounts-profile-modify')
  60. class PasswordChangeView(email_accounts_views.PasswordChangeView):
  61. success_url = reverse_lazy('dtracker-accounts-profile-password-change')
  62. class AccountProfile(email_accounts_views.AccountProfile):
  63. pass
  64. class ManageAccountEmailsView(ConfirmationRenderMixin,
  65. email_accounts_views.ManageAccountEmailsView):
  66. success_url = reverse_lazy('dtracker-accounts-manage-emails')
  67. merge_accounts_url = reverse_lazy('dtracker-accounts-merge-confirmation')
  68. confirmation_email_subject = 'Add Email To {name} Account'.format(
  69. name=settings.GET_INSTANCE_NAME())
  70. confirmation_email_from_address = settings.DISTRO_TRACKER_CONTACT_EMAIL
  71. class AccountMergeConfirmView(ConfirmationRenderMixin,
  72. email_accounts_views.AccountMergeConfirmView):
  73. success_url = reverse_lazy('dtracker-accounts-merge-confirmed')
  74. confirmation_email_subject = 'Merge {name} Accounts'.format(
  75. name=settings.GET_INSTANCE_NAME())
  76. confirmation_email_from_address = settings.DISTRO_TRACKER_CONTACT_EMAIL
  77. class AccountMergeFinalize(email_accounts_views.AccountMergeFinalize):
  78. success_url = reverse_lazy('dtracker-accounts-merge-finalized')
  79. class AccountMergeConfirmedView(email_accounts_views.AccountMergeConfirmedView):
  80. template_name = 'accounts/tracker-accounts-merge-confirmed.html'
  81. class ConfirmAddAccountEmail(email_accounts_views.ConfirmAddAccountEmail):
  82. pass
  83. class SubscriptionsView(LoginRequiredMixin, View):
  84. """
  85. Displays a user's subscriptions.
  86. This includes both direct package subscriptions and team memberships.
  87. """
  88. template_name = 'accounts/subscriptions.html'
  89. def get(self, request):
  90. user = request.user
  91. user_emails = UserEmail.objects.filter(user=user)
  92. # Map users emails to the subscriptions of that email
  93. for user_email in user_emails:
  94. EmailSettings.objects.get_or_create(user_email=user_email)
  95. subscriptions = {
  96. user_email: {
  97. 'subscriptions': sorted([
  98. subscription for subscription
  99. in user_email.emailsettings.subscription_set.all()
  100. ], key=lambda sub: sub.package.name),
  101. 'team_memberships': sorted([
  102. membership for membership in user_email.membership_set.all()
  103. ], key=lambda m: m.team.name)
  104. }
  105. for user_email in user_emails
  106. }
  107. # Initializing session variable if not set.
  108. request.session.setdefault('selected_emails', [str(user_emails[0])])
  109. return render(request, self.template_name, {
  110. 'subscriptions': subscriptions,
  111. 'selected_emails': request.session['selected_emails']
  112. })
  113. class UserEmailsView(LoginRequiredMixin, View):
  114. """
  115. Returns a JSON encoded list of the currently logged in user's emails.
  116. """
  117. def get(self, request):
  118. user = request.user
  119. return render_to_json_response([
  120. email.email for email in user.emails.all()
  121. ])
  122. class SubscribeUserToPackageView(LoginRequiredMixin, View):
  123. """
  124. Subscribes the user to a package.
  125. The user whose email address is provided must currently be logged in.
  126. """
  127. def post(self, request):
  128. package = request.POST.get('package', None)
  129. emails = request.POST.getlist('email', None)
  130. if not package or not emails:
  131. raise Http404
  132. # Remember selected emails via session variable
  133. request.session['selected_emails'] = emails
  134. # Check whether the logged in user is associated with the given emails
  135. users_emails = [e.email for e in request.user.emails.all()]
  136. for email in emails:
  137. if email not in users_emails:
  138. return HttpResponseForbidden()
  139. # Create the subscriptions
  140. json_result = {'status': 'ok'}
  141. try:
  142. for email in emails:
  143. Subscription.objects.create_for(
  144. package_name=package,
  145. email=email)
  146. except ValidationError as e:
  147. json_result['status'] = 'failed'
  148. json_result['error'] = e.message
  149. if request.is_ajax():
  150. return render_to_json_response(json_result)
  151. else:
  152. if 'error' in json_result:
  153. return HttpResponseBadRequest(json_result['error'])
  154. next = request.POST.get('next', None)
  155. if not next:
  156. return redirect('dtracker-package-page', package_name=package)
  157. return redirect(next)
  158. class UnsubscribeUserView(LoginRequiredMixin, View):
  159. """
  160. Unsubscribes the currently logged in user from the given package.
  161. An email can be optionally provided in which case only the given email is
  162. unsubscribed from the package, if the logged in user owns it.
  163. """
  164. def post(self, request):
  165. if 'package' not in request.POST:
  166. raise Http404
  167. package = request.POST['package']
  168. user = request.user
  169. if 'email' not in request.POST:
  170. # Unsubscribe all the user's emails from the package
  171. user_emails = UserEmail.objects.filter(user=user)
  172. qs = Subscription.objects.filter(
  173. email_settings__user_email__in=user_emails,
  174. package__name=package)
  175. else:
  176. # Unsubscribe only the given email from the package
  177. qs = Subscription.objects.filter(
  178. email_settings__user_email__email=request.POST['email'],
  179. package__name=package)
  180. qs.delete()
  181. if request.is_ajax():
  182. return render_to_json_response({
  183. 'status': 'ok',
  184. })
  185. else:
  186. if 'next' in request.POST:
  187. return redirect(request.POST['next'])
  188. else:
  189. return redirect('dtracker-package-page', package_name=package)
  190. class UnsubscribeAllView(LoginRequiredMixin, View):
  191. """
  192. The view unsubscribes the currently logged in user from all packages.
  193. If an optional ``email`` POST parameter is provided, only removes all
  194. subscriptions for the given emails.
  195. """
  196. def post(self, request):
  197. user = request.user
  198. if 'email' not in request.POST:
  199. emails = user.emails.all()
  200. else:
  201. emails = user.emails.filter(email__in=request.POST.getlist('email'))
  202. # Remove all the subscriptions
  203. Subscription.objects.filter(
  204. email_settings__user_email__in=emails).delete()
  205. if request.is_ajax():
  206. return render_to_json_response({
  207. 'status': 'ok',
  208. })
  209. else:
  210. if 'next' in request.POST:
  211. return redirect(request.POST['next'])
  212. else:
  213. return redirect('dtracker-index')
  214. class ChooseSubscriptionEmailView(LoginRequiredMixin, View):
  215. """
  216. Lets the user choose which email to subscribe to a package with.
  217. This is an alternative view when JS is disabled and the appropriate choice
  218. cannot be offered in a popup.
  219. """
  220. template_name = 'accounts/choose-email.html'
  221. def get(self, request):
  222. if 'package' not in request.GET:
  223. raise Http404
  224. return render(request, self.template_name, {
  225. 'package': request.GET['package'],
  226. 'emails': request.user.emails.all(),
  227. })
  228. class ModifyKeywordsView(LoginRequiredMixin, View):
  229. """
  230. Lets the logged in user modify his default keywords or
  231. subscription-specific keywords.
  232. """
  233. def get_keywords(self, keywords):
  234. """
  235. :returns: :class:`Keyword <distro_tracker.core.models.Keyword>`
  236. instances for the given keyword names.
  237. """
  238. return Keyword.objects.filter(name__in=keywords)
  239. def modify_default_keywords(self, email, keywords):
  240. try:
  241. user_email = UserEmail.objects.get(user=self.user, email=email)
  242. except (UserEmail.DoesNotExist):
  243. return HttpResponseForbidden()
  244. email_settings, _ = \
  245. EmailSettings.objects.get_or_create(user_email=user_email)
  246. email_settings.default_keywords = self.get_keywords(keywords)
  247. return self.render_response()
  248. def modify_subscription_keywords(self, email, package, keywords):
  249. try:
  250. user_email = UserEmail.objects.get(user=self.user, email=email)
  251. except (UserEmail.DoesNotExist):
  252. return HttpResponseForbidden()
  253. email_settings, _ = \
  254. EmailSettings.objects.get_or_create(user_email=user_email)
  255. subscription = get_object_or_404(
  256. Subscription, email_settings__user_email=user_email,
  257. package__name=package)
  258. subscription.keywords.clear()
  259. for keyword in self.get_keywords(keywords):
  260. subscription.keywords.add(keyword)
  261. return self.render_response()
  262. def render_response(self):
  263. if self.request.is_ajax():
  264. return render_to_json_response({
  265. 'status': 'ok',
  266. })
  267. else:
  268. if 'next' in self.request.POST:
  269. return redirect(self.request.POST['next'])
  270. else:
  271. return redirect('dtracker-index')
  272. def post(self, request):
  273. if 'email' not in request.POST or 'keyword[]' not in request.POST:
  274. raise Http404
  275. self.user = request.user
  276. self.request = request
  277. email = request.POST['email']
  278. keywords = request.POST.getlist('keyword[]')
  279. if 'package' in request.POST:
  280. return self.modify_subscription_keywords(
  281. email, request.POST['package'], keywords)
  282. else:
  283. return self.modify_default_keywords(email, keywords)
  284. def get(self, request):
  285. if 'email' not in request.GET:
  286. raise Http404
  287. email = request.GET['email']
  288. try:
  289. user_email = request.user.emails.get(email=email)
  290. except UserEmail.DoesNotExist:
  291. return HttpResponseForbidden()
  292. if 'package' in request.GET:
  293. package = request.GET['package']
  294. subscription = get_object_or_404(
  295. Subscription, email_settings__user_email=user_email,
  296. package__name=package)
  297. context = {
  298. 'post': {
  299. 'email': email,
  300. 'package': package,
  301. },
  302. 'package': package,
  303. 'user_keywords': subscription.keywords.all(),
  304. }
  305. else:
  306. context = {
  307. 'post': {
  308. 'email': email,
  309. },
  310. 'user_keywords':
  311. user_email.emailsettings.default_keywords.all(),
  312. }
  313. context.update({
  314. 'keywords': Keyword.objects.order_by('name').all(),
  315. 'email': email,
  316. })
  317. return render(request, 'accounts/modify-subscription.html', context)