1# -*- coding: utf-8 -*-
2# Copyright (C) 1998-2021 by the Free Software Foundation, Inc.
3#
4# This file is part of Postorius.
5#
6# Postorius is free software: you can redistribute it and/or modify it under
7# the terms of the GNU General Public License as published by the Free
8# Software Foundation, either version 3 of the License, or (at your option)
9# any later version.
10#
11# Postorius is distributed in the hope that it will be useful, but WITHOUT
12# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
14# more details.
15#
16# You should have received a copy of the GNU General Public License along with
17# Postorius.  If not, see <http://www.gnu.org/licenses/>.
18
19
20import logging
21from urllib.error import HTTPError
22
23from django.contrib import messages
24from django.contrib.auth.decorators import login_required
25from django.forms import formset_factory
26from django.http import Http404
27from django.shortcuts import render
28from django.urls import reverse, reverse_lazy
29from django.utils.decorators import method_decorator
30from django.utils.translation import gettext as _
31from django.views.generic import FormView
32
33from allauth.account.models import EmailAddress
34from django_mailman3.lib.mailman import get_mailman_client, get_mailman_user
35
36from postorius.forms import (
37    ChangeSubscriptionForm, UserPreferences, UserPreferencesFormset)
38from postorius.models import List, SubscriptionMode
39from postorius.utils import set_preferred
40from postorius.views.generic import MailmanClientMixin
41
42
43logger = logging.getLogger(__name__)
44
45
46class UserPreferencesView(FormView, MailmanClientMixin):
47    """Generic view for the logged-in user's various preferences."""
48
49    form_class = UserPreferences
50
51    def get_context_data(self, **kwargs):
52        data = super(UserPreferencesView, self).get_context_data(**kwargs)
53        data['mm_user'] = self.mm_user
54        return data
55
56    def get_form_kwargs(self):
57        kwargs = super(UserPreferencesView, self).get_form_kwargs()
58        kwargs['preferences'] = self._get_preferences()
59        return kwargs
60
61    def _set_view_attributes(self, request, *args, **kwargs):
62        self.mm_user = get_mailman_user(request.user)
63
64    @method_decorator(login_required)
65    def dispatch(self, request, *args, **kwargs):
66        self._set_view_attributes(request, *args, **kwargs)
67        return super(UserPreferencesView, self).dispatch(
68            request, *args, **kwargs)
69
70    def form_valid(self, form):
71        try:
72            form.save()
73        except HTTPError as e:
74            messages.error(self.request, e.msg)
75        if form.has_changed():
76            messages.success(
77                self.request, _('Your preferences have been updated.'))
78        else:
79            messages.info(self.request, _('Your preferences did not change.'))
80        return super(UserPreferencesView, self).form_valid(form)
81
82
83class UserMailmanSettingsView(UserPreferencesView):
84    """The logged-in user's global Mailman preferences."""
85
86    form_class = UserPreferences
87    template_name = 'postorius/user/mailman_settings.html'
88    success_url = reverse_lazy('user_mailmansettings')
89
90    def _get_preferences(self):
91        # Get the defaults and pre-populate so view shows them
92        combinedpreferences = self._get_combined_preferences()
93        for key in combinedpreferences:
94            if key != "self_link":
95                self.mm_user.preferences[key] = combinedpreferences[key]
96
97        # This is a bit of a hack so preferences behave as users expect
98        # We probably don't want to save, only display here
99        # but this means that whatever preferences the users see first are
100        # the ones they have unless they explicitly change them
101        self.mm_user.preferences.save()
102
103        return self.mm_user.preferences
104
105    def _get_combined_preferences(self):
106        # Get layers of default preferences to match how they are applied
107        # We ignore self_link as we don't want to over-write it
108        defaultpreferences = get_mailman_client().preferences
109        combinedpreferences = {}
110        for key in defaultpreferences:
111            if key != "self_link":
112                combinedpreferences[key] = defaultpreferences[key]
113
114        # Clobber defaults with any preferences already set
115        for key in self.mm_user.preferences:
116            if key != "self_link":
117                combinedpreferences[key] = self.mm_user.preferences[key]
118
119        return(combinedpreferences)
120
121
122class UserAddressPreferencesView(UserPreferencesView):
123    """The logged-in user's address-based Mailman Preferences."""
124
125    template_name = 'postorius/user/address_preferences.html'
126    success_url = reverse_lazy('user_address_preferences')
127
128    def get_form_class(self):
129        return formset_factory(
130            UserPreferences, formset=UserPreferencesFormset, extra=0)
131
132    def _get_preferences(self):
133        return [address.preferences for address in self.mm_user.addresses]
134
135    def _get_combined_preferences(self):
136        # grab the default preferences
137        defaultpreferences = get_mailman_client().preferences
138
139        # grab your global preferences
140        globalpreferences = self.mm_user.preferences
141
142        # start a new combined preferences object
143        combinedpreferences = []
144
145        for address in self.mm_user.addresses:
146            # make a per-address prefs object
147            prefs = {}
148
149            # initialize with default preferences
150            for key in defaultpreferences:
151                if key != "self_link":
152                    prefs[key] = defaultpreferences[key]
153
154            # overwrite with user's global preferences
155            for key in globalpreferences:
156                if key != "self_link":
157                    prefs[key] = globalpreferences[key]
158
159            # overwrite with address-specific preferences
160            for key in address.preferences:
161                if key != "self_link":
162                    prefs[key] = address.preferences[key]
163            combinedpreferences.append(prefs)
164
165            # put the combined preferences back on the original object
166            for key in prefs:
167                if key != "self_link":
168                    address.preferences[key] = prefs[key]
169
170        return combinedpreferences
171
172    def get_context_data(self, **kwargs):
173        data = super(UserAddressPreferencesView, self).get_context_data(
174            **kwargs)
175        data['formset'] = data.pop('form')
176        for form, address in list(zip(
177                data['formset'].forms, self.mm_user.addresses)):
178            form.address = address
179        return data
180
181
182class UserListOptionsView(UserPreferencesView):
183    """The logged-in user's subscription preferences."""
184
185    form_class = UserPreferences
186    template_name = 'postorius/user/list_options.html'
187
188    def _get_subscription(self):
189        subscription = None
190        for s in self.mm_user.subscriptions:
191            if s.role == 'member' and s.list_id == self.mlist.list_id:
192                subscription = s
193                break
194        if not subscription:
195            raise Http404(_('Subscription does not exist'))
196        return subscription
197
198    def _set_view_attributes(self, request, *args, **kwargs):
199        super(UserListOptionsView, self)._set_view_attributes(
200            request, *args, **kwargs)
201        self.mlist = List.objects.get_or_404(fqdn_listname=kwargs['list_id'])
202        self.subscription = self._get_subscription()
203        if (self.subscription.subscription_mode ==
204                SubscriptionMode.as_user.name):
205            self.subscriber = self.subscription.user.user_id
206        else:
207            self.subscriber = self.subscription.email
208
209    def _get_preferences(self):
210        return self.subscription.preferences
211
212    def get_context_data(self, **kwargs):
213        data = super(UserListOptionsView, self).get_context_data(**kwargs)
214        data['mlist'] = self.mlist
215        user_emails = EmailAddress.objects.filter(
216            user=self.request.user, verified=True).order_by(
217            "email").values_list("email", flat=True)
218        mm_user = get_mailman_user(self.request.user)
219        primary_email = None
220        if mm_user.preferred_address is None:
221            primary_email = set_preferred(self.request.user, mm_user)
222        else:
223            primary_email = mm_user.preferred_address.email
224        data['change_subscription_form'] = ChangeSubscriptionForm(
225            user_emails, mm_user.user_id, primary_email,
226            initial={'subscriber': self.subscriber})
227        return data
228
229    def get_success_url(self):
230        return reverse(
231            'user_list_options', kwargs=dict(list_id=self.mlist.list_id))
232
233
234class UserSubscriptionPreferencesView(UserPreferencesView):
235    """The logged-in user's subscription-based Mailman Preferences."""
236
237    template_name = 'postorius/user/subscription_preferences.html'
238    success_url = reverse_lazy('user_subscription_preferences')
239
240    def _get_subscriptions(self):
241        subscriptions = []
242        for s in self.mm_user.subscriptions:
243            if s.role != 'member':
244                continue
245            subscriptions.append(s)
246        return subscriptions
247
248    def _set_view_attributes(self, request, *args, **kwargs):
249        super(UserSubscriptionPreferencesView, self)._set_view_attributes(
250            request, *args, **kwargs)
251        self.subscriptions = self._get_subscriptions()
252
253    def get_form_class(self):
254        return formset_factory(
255            UserPreferences, formset=UserPreferencesFormset, extra=0)
256
257    def _get_preferences(self):
258        return [sub.preferences for sub in self.subscriptions]
259
260    def _get_combined_preferences(self):
261        # grab the default preferences
262        defaultpreferences = get_mailman_client().preferences
263
264        # grab your global preferences
265        globalpreferences = self.mm_user.preferences
266
267        # start a new combined preferences object
268        combinedpreferences = []
269
270        for sub in self.subscriptions:
271            # make a per-address prefs object
272            prefs = {}
273
274            # initialize with default preferences
275            for key in defaultpreferences:
276                if key != "self_link":
277                    prefs[key] = defaultpreferences[key]
278
279            # overwrite with user's global preferences
280            for key in globalpreferences:
281                if key != "self_link":
282                    prefs[key] = globalpreferences[key]
283
284            # overwrite with address-based preferences
285            # There is currently no better way to do this,
286            # we may consider revisiting.
287            addresspreferences = {}
288            for address in self.mm_user.addresses:
289                if sub.email == address.email:
290                    addresspreferences = address.preferences
291
292            for key in addresspreferences:
293                if key != "self_link":
294                    prefs[key] = addresspreferences[key]
295
296            # overwrite with subscription-specific preferences
297            for key in sub.preferences:
298                if key != "self_link":
299                    prefs[key] = sub.preferences[key]
300
301            combinedpreferences.append(prefs)
302
303        return combinedpreferences
304        # return [sub.preferences for sub in self.subscriptions]
305
306    def get_context_data(self, **kwargs):
307        data = super(UserSubscriptionPreferencesView, self).get_context_data(
308            **kwargs)
309        data['formset'] = data.pop('form')
310        for form, subscription in list(zip(
311                data['formset'].forms, self.subscriptions)):
312            form.list_id = subscription.list_id
313        return data
314
315
316@login_required
317def user_subscriptions(request):
318    """Shows the subscriptions of a user."""
319    mm_user = get_mailman_user(request.user)
320    memberships = [m for m in mm_user.subscriptions]
321    return render(request, 'postorius/user/subscriptions.html',
322                  {'memberships': memberships})
323