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