Add UI for email forwarding
authorMichael Spang <mspang@csclub.uwaterloo.ca>
Sun, 23 Aug 2009 15:15:03 +0000 (11:15 -0400)
committerMichael Spang <mspang@csclub.uwaterloo.ca>
Sun, 23 Aug 2009 17:40:08 +0000 (13:40 -0400)
We nag users to update their forwarding address every time they renew
membership.

ceo/members.py
ceo/urwid/main.py
ceo/urwid/renew.py

index fbec163..1154257 100644 (file)
@@ -9,7 +9,7 @@ Transactions are used in each method that modifies the database.
 Future changes to the members database that need to be atomic
 must also be moved into this module.
 """
-import os, re, subprocess, ldap
+import os, re, subprocess, ldap, socket
 from ceo import conf, ldapi, terms, remote, ceo_pb2
 from ceo.excep import InvalidArgument
 
@@ -164,6 +164,54 @@ def create_member(username, password, name, program, email):
         raise MemberException(e)
 
 
+def check_email(email):
+    match = re.match('^\S+?@(\S+)$', email)
+    if not match:
+        return 'Invalid email address'
+
+    # some characters are treated specially in .forward
+    for c in email:
+        if c in ('"', "'", ',', '|', '$', '/', '#', ':'):
+            return 'Invalid character in address: %s' % c
+
+    host = match.group(1)
+    try:
+        ip = socket.gethostbyname(host)
+    except:
+        return 'Invalid host: %s' % host
+
+
+def current_email(username):
+    fwdpath = '%s/%s/.forward' % (cfg['member_home'], username)
+    try:
+        fwd = open(fwdpath).read().strip()
+        if not check_email(fwd):
+            return fwd
+    except OSError:
+        pass
+    except IOError:
+        pass
+
+
+def change_email(username, forward):
+    try:
+        request = ceo_pb2.UpdateMail()
+        request.username = username
+        request.forward = forward
+
+        out = remote.run_remote('mail', request.SerializeToString())
+
+        response = ceo_pb2.AddUserResponse()
+        response.ParseFromString(out)
+
+        if any(message.status != 0 for message in response.messages):
+            return '\n'.join(message.message for message in response.messages)
+    except remote.RemoteException, e:
+        raise MemberException(e)
+    except OSError, e:
+        raise MemberException(e)
+
+
 def get(userid):
     """
     Look up attributes of a member by userid.
index 8528a91..252ddff 100644 (file)
@@ -87,15 +87,19 @@ def renew_member(*args, **kwargs):
     push_wizard("Renew Membership", [
         renew.IntroPage,
         renew.UserPage,
+        renew.EmailPage,
+        renew.EmailDonePage,
         renew.TermPage,
         renew.PayPage,
         renew.EndPage,
-    ])
+    ], (60, 15))
 
 def renew_club_user(*args, **kwargs):
     push_wizard("Renew Club Rep Account", [
         renew.ClubUserIntroPage,
         renew.UserPage,
+        renew.EmailPage,
+        renew.EmailDonePage,
         (renew.TermPage, "clubuser"),
         (renew.EndPage, "clubuser"),
     ], (60, 15))
index 870d3fb..ac0279a 100644 (file)
@@ -49,6 +49,84 @@ class UserPage(WizardPanel):
             self.focus_widget(self.userid)
             return True
 
+class EmailPage(WizardPanel):
+    def init_widgets(self):
+        self.email = SingleEdit("Email: ")
+
+        self.widgets = [
+            urwid.Text( "Mail Forwarding" ),
+            urwid.Divider(),
+            urwid.Text("Please ensure the forwarding address for "
+                       "your CSC email is up to date."),
+            urwid.Divider(),
+            urwid.Text("Warning: Changing this overwrites ~/.forward"),
+            urwid.Divider(),
+            self.email,
+        ]
+    def activate(self):
+        cfwd = members.current_email(self.state['userid'])
+        self.state['old_forward'] = cfwd if cfwd else ''
+        self.email.set_edit_text(self.state['old_forward'])
+    def check(self):
+        fwd = self.email.get_edit_text().strip().lower()
+        if fwd:
+            msg = members.check_email(fwd)
+            if msg:
+                set_status(msg)
+                return True
+            if fwd == '%s@csclub.uwaterloo.ca' % self.state['userid']:
+                set_status('You cannot forward your address to itself. Leave it blank to disable forwarding.')
+                return True
+        self.state['new_forward'] = fwd
+
+class EmailDonePage(WizardPanel):
+    def init_widgets(self):
+        self.status = urwid.Text("")
+        self.widgets = [
+            urwid.Text("Mail Forwarding"),
+            urwid.Divider(),
+            self.status,
+        ]
+    def focusable(self):
+        return False
+    def activate(self):
+        if self.state['old_forward'] == self.state['new_forward']:
+            if self.state['old_forward']:
+                self.status.set_text(
+                    'You have chosen to leave your forwarding address '
+                    'as %s. Make sure to check this email for updates '
+                    'from the CSC.' % self.state['old_forward'])
+            else:
+                self.status.set_text(
+                    'You have chosen not to set a forwarding address. '
+                    'Please check your CSC email regularly (via IMAP, POP, or locally) '
+                    'for updates from the CSC.'
+                    '\n\n'
+                    'Note: If you do have a ~/.forward, we were not able to read it or '
+                    'it was not a single email address. Do not worry, we have left it '
+                    'as is.')
+        else:
+            try:
+                msg = members.change_email(self.state['userid'], self.state['new_forward'])
+                if msg:
+                    self.status.set_text("Errors occured updating your forwarding address:"
+                                         "\n\n%s" % msg)
+                else:
+                    if self.state['new_forward']:
+                        self.status.set_text(
+                            'Your email forwarding address has been successfully set '
+                            'to %s. Test it out by emailing %s@csclub.uwaterloo.ca and '
+                            'making sure you receive it at your forwarding address.'
+                            % (self.state['new_forward'], self.state['userid']))
+                    else:
+                        self.status.set_text(
+                            'Your email forwarding address has been successfully cleared. '
+                            'Please check your CSC email regularly (via IMAP, POP, or locally) '
+                            'for updates from the CSC.')
+            except Exception, e:
+                self.status.set_text(
+                    'An exception occured updating your email:\n\n%s' % e)
+
 class TermPage(WizardPanel):
     def __init__(self, state, utype='member'):
         self.utype = utype