Added ability for libcom to send emails to those with overdue books
authorConnor Murphy <csfmurph@csclub.uwaterloo.ca>
Sat, 4 Feb 2017 04:55:51 +0000 (23:55 -0500)
committerCharlie Wang <s455wang@csclub.uwaterloo.ca>
Sat, 4 Feb 2017 05:24:15 +0000 (00:24 -0500)
librarian
library/interface/form.py
library/interface/sendemails.py [new file with mode: 0644]

index a0abf61..7b3626d 100755 (executable)
--- a/librarian
+++ b/librarian
@@ -6,6 +6,7 @@ import library.interface.browser as browser
 import library.interface.form as form
 import library.interface.help_bar as helpBar
 import library.interface.checkout as co
+import library.interface.sendemails as sendemails
 
 from  library import book_data
 
@@ -186,6 +187,11 @@ def catMenu():
     cat.eventLoop()
     cat.clear()
 
+def email_menu():
+    w=curses.newwin(1,1)
+    (my,mx)=stdscr.getmaxyx()
+    sendemails.sendemails_procedure(w,hb,my//2,mx//2,mx)
+
 
 if __name__ == "__main__":
     db.initializeDatabase()
@@ -197,6 +203,7 @@ if __name__ == "__main__":
          ("",exit),
          ("Check Out a Book", co_menu),
          ("Return a Book", return_menu),
+         ("Send Overdue Email Reminders", email_menu),
          ("",exit),
          ("View Checked Out Books", checkedout_menu),
          ("View On Shelf Books", onshelf_menu),
index 652f377..7dc9d37 100644 (file)
@@ -107,6 +107,13 @@ class TextEntry:
         self.value = self.value[:c] + self.value[c+1:]
         self._mv_cursor(0)
 
+class PasswordEntry(TextEntry):
+  def redraw(self):
+        self.w.addnstr(self.y,self.x, " "*self.width, self.width)
+        if self.focus:
+            self.w.chgat(self.y, self.x, self.width, curses.A_UNDERLINE)
+            curses.curs_set(1)
+
 
 
 class FormWindow:
@@ -276,8 +283,18 @@ class FormWindow:
             if self.bt==-1:
                 self.entries[self.hl].handle_input(ch)
 
+class LoginForm(FormWindow):
+  caption = "Enter your csclub login to access the mail server"
+  blabel = "Login"
+  labels = ["ID", "Password"]
 
+  def _make_entries(self):
+        self.entries = []
+        self.entries.append(TextEntry(self.w))
+        self.entries.append(PasswordEntry(self.w))
 
+  def _return_values(self):
+    return [self.entries[0].value, self.entries[1].value]
 
 class BookForm(FormWindow):
     caption = "Add a Book"
diff --git a/library/interface/sendemails.py b/library/interface/sendemails.py
new file mode 100644 (file)
index 0000000..2356f16
--- /dev/null
@@ -0,0 +1,148 @@
+#Import smtp for the email sending function
+import smtplib
+
+#Import argparse to parse command line args
+import argparse
+
+#Import sys so that we can exit when given bad command line args
+import sys
+
+#Import datetime and time to check dates
+from datetime import date
+import time
+
+#Import getpass for password input
+import getpass
+
+#Import subprocess for validation of if a user is in a group
+import subprocess
+
+#Import librarian permissions
+from library import permissions
+from library.exceptions import *
+import library.database as db
+from library.interface.form import FormWindow,BookForm, LoginForm, catch_error_with, error_form
+
+#Constants
+DEFAULT_DAY_VALUE = 21
+MAX_LOGIN_ATTEMPTS = 3
+
+class DaysForm(FormWindow):
+  caption = "Enter the max number of days a book can be signed out for"
+  blabel = "Enter"
+  labels = ["Days"]
+
+  def _return_values(self):
+    return self.entries[0].value
+
+
+class NameForm(FormWindow):
+  caption = "Enter the name you want in the signature line for the email"
+  blabel = "Enter"
+  labels = ["Name"]
+  
+  def _return_values(self):
+    if self.entries[0].value is "":
+      return "Librarian"
+    else:
+      return self.entries[0].value
+
+#Private functions
+def _send_email(quest_id: str, signed_out_date: str, max_days_can_be_signed_out:int, librarianName: str, book_name_list) -> None:
+    """Sends an email to quest_id@csclub.uwaterloo.ca if the date the book
+    was signed out exceeds the days it is supposed to be signed out for"""
+
+    email_address = quest_id + "@csclub.uwaterloo.ca"
+    book_name = "".join(book_name_list)
+
+    #Determine the days the book has been signed out
+    date_tokens = signed_out_date.split("-")
+    date_signed_out = date(int(date_tokens[0]), int(date_tokens[1]), int(date_tokens[2]))
+    days_signed_out = date.fromtimestamp(time.time()) - date_signed_out
+  
+    if days_signed_out.days > max_days_can_be_signed_out:
+        email_message_body = ("Hi " + quest_id + ",\n\n" +  "Our records indicate that you "
+                             "have had the book " + book_name + " signed out for " + str(str(days_signed_out).split(" ")[:1])[2:-2] + 
+                              " days.\n\n" +  "Please return the book to the CS Club office "
+                              "(MC 3036) at your earliest convenience.\n\n" + "Thanks,\n\n" + 
+                               librarianName + "\n" + "Computer Science Club | University of Waterloo\n" 
+                               "librarian@csclub.uwaterloo.ca")
+           
+        email_message_subject = "Overdue book: {}".format(book_name)
+        email_message = "Subject: {}\n\n{}".format(email_message_subject, email_message_body)
+  
+        server.sendmail("librarian@csclub.uwaterloo.ca", email_address, email_message)
+  
+#Public functions    
+@permissions.check_permissions(permissions.PERMISSION_LIBCOM)
+@catch_error_with(lambda w, hb, *args : (w, hb, None))
+def sendemails_procedure(w, hb, cy, cx, mx):
+  """Procedure to send emails to those with overdue books
+
+    w:      ncurses window for the routine
+    cy,cx:  centre coordinates of the screen
+    mx:     max width of screen
+    """
+  
+  #Get the max days a book can be signed out for
+  step1 = DaysForm(w,hb,width=mx-20)
+  (r,c)=w.getmaxyx()
+  w.mvwin(cy-r//2,cx-c//2)
+  days = step1.event_loop()
+  step1.clear()
+
+  #Set the days to a default value if the value given is less than 0
+  if not isinstance(days, int) or days < 0:
+      days = DEFAULT_DAY_VALUE
+
+  #Get the name of the librarain
+  step2 = NameForm(w,hb,width=mx-20)
+  (r,c)=w.getmaxyx()
+  w.mvwin(cy-r//2,cx-c//2)
+  librarianName = step2.event_loop()
+  step2.clear()
+
+  #Set up email
+  global server
+  server = smtplib.SMTP_SSL("mail.csclub.uwaterloo.ca", 465)
+
+  #Attempt to login 3 times
+  loginAttempts = 0
+  while loginAttempts < MAX_LOGIN_ATTEMPTS:
+
+    #Get the user's login info to login to the mail server
+    step3 = LoginForm(w,hb,width=mx-20)
+    (r,c)=w.getmaxyx()
+    w.mvwin(cy-r//2,cx-c//2)
+    loginInfo = step3.event_loop()
+    step3.clear()
+
+    csclubID = loginInfo[0]
+    csclubPwd = loginInfo[1]
+  
+    try:
+      server.login(csclubID, csclubPwd)
+      break
+    except smtplib.SMTPAuthenticationError:
+      loginAttempts += 1
+
+  #Check to see if login failed
+  if loginAttempts >= MAX_LOGIN_ATTEMPTS:
+     return ""
+  
+  #Get the books that are signed out
+  signed_out_books = db.get_checkedout_books()
+
+  for data in signed_out_books:
+    quest_id = data["uwid"]
+    date = str(data["date"]).split(" ")[0]
+    book_name_with_spaces = data["title"]
+
+    _send_email(str(quest_id), str(date), days, str(librarianName), book_name_with_spaces)
+  #Exit from the email server    
+  server.quit()
+
+  return ""
+
+