#!/usr/bin/env python """ This program gets the members from the MySQL Database that need renewal. By starting with --mode=day you can select only those members that need to renew today. With --mode=year you get the members that need to renew this year, i.e. whole 2011. One can send email, too. Use the --send-mail switch to send email. If you want to test your setup, you can use --one-only=your@address.com to test whether email actually works or what From address is used, which is configurable with --from-address=other@address.com. In order to work properly, you need to have your MySQL client configured properly, i.e. ~/.my.cnf should be set up with the appropriate credentials: [client] host=button-back user=anonvoting password=foobar default-character-set=utf8 The reason to call MySQL client and not use the Python library is mainly, that the MySQL bindings are not installed. """ import datetime try: from email.mime.text import MIMEText from email.mime.nonmultipart import MIMENonMultipart from email.charset import Charset except ImportError: from email.MIMEText import MIMEText from email.Charset import Charset from email.MIMENonMultipart import MIMENonMultipart import logging from optparse import OptionParser import smtplib import StringIO import subprocess import sys import tempfile __author__ = "Tobias Mueller" __copyright__ = "Copyright 2011, The GNOME Project" __credits__ = ["Tobias Mueller",] __license__ = "GPLv3+" __version__ = "1.0.0" __maintainer__ = "Tobias Mueller" __email__ = "tobiasmue@gnome.org" TEMPLATE = ''' Dear %(firstname)s, the last time, your GNOME Foundation Membership was renewed, was on %(last_renewed_on)s. The term of a membership is two years. If you want to continue being a member, you have to renew your membership. Please see http://foundation.gnome.org/membership/ for details. Thanks, The GNOME Membership and Elections Committee '''.strip() log = logging.getLogger() class MTText(MIMEText): def __init__(self, _text, _subtype='plain', _charset='utf-8'): if not isinstance(_charset, Charset): _charset = Charset(_charset) if isinstance(_text,unicode): _text = _text.encode(_charset.input_charset) MIMENonMultipart.__init__(self, 'text', _subtype, **{'charset': _charset.input_charset}) self.set_payload(_text, _charset) def send_email(to, subject, emailtext, from_address = None, smtp_server = 'localhost'): log = logging.getLogger('eMail') s = None from_address = from_address or "GNOME Membership and Elections Committee " msg = MTText(emailtext) msg['To'] = to msg['From'] = from_address msg['Subject'] = subject msgstr = msg.as_string() if s is None: s = smtplib.SMTP() s.connect(smtp_server) try: log.info('Trying to send to %s, %s', to, subject) s.sendmail(from_address, [to,], msgstr) except smtplib.SMTPException,e : log.warning("Error: Could not send to %s!" % (to,)) raise e #if s: # s.quit() class Member(object): def __init__(self, firstname, lastname, email, token_or_last_renewed_on): self.firstname = firstname self.lastname = lastname self.email = email self.token_or_last_renewed_on = token_or_last_renewed_on @classmethod def from_csv(cls, csvstring): firstname, lastname, email, token_or_last_renewed_on = csvstring.strip().split(';') return Member(firstname, lastname, email, token_or_last_renewed_on) def execute_query(query): log.debug('MySQL Query: %s', query) DATABASE = "foundation" mysql_p = subprocess.Popen(['mysql', DATABASE], stdin=subprocess.PIPE, stdout=subprocess.PIPE) SQL_result, SQL_error = mysql_p.communicate(query) if SQL_error: sys.stderr.write('Error Executing SQL: %s' % SQL_error) if not SQL_result: log.info('NULL Result from SQL') infile = StringIO.StringIO("") else: infile = StringIO.StringIO(SQL_result) _ = infile.next() # The first line is garbage, I think it's MySQL output return infile def get_members_which_need_renewal(mode): """ Generates and executes a SQL Query which asks the foundation's database for members that need to renew. The base date is calculated from the mode argument: election: Last years 07-01 until this years 07-01 year: Beginning of the year, i.e. 01.01. until 31.12. month: From 1st of this month to end of this month day: Members that need to renew this day """ ELECTION_FROM_DATE = """CONCAT( YEAR( DATE_SUB(CURDATE(), INTERVAL 1 YEAR) ), '-07-01') """.strip()# Last years July, 1st ELECTION_TO_DATE = """CONCAT(YEAR(CURDATE()), '-07-01')""" # This years July, 1st DAY_FROM_DATE = """DATE_FORMAT(NOW(),"%Y-%m-%d")""" DAY_TO_DATE = DAY_FROM_DATE MONTH_FROM_DATE = """DATE_FORMAT(NOW(),"%Y-%m-01")""" MONTH_TO_DATE = """LAST_DAY(NOW())""" YEAR_FROM_DATE = """DATE_FORMAT(NOW(),"%Y-01-01")""" YEAR_TO_DATE = """DATE_FORMAT(NOW(),"%Y-12-31")""" (mysql_from_date, mysql_to_date) = { 'election': (ELECTION_FROM_DATE, ELECTION_TO_DATE), 'day': (DAY_FROM_DATE,DAY_TO_DATE), 'month': (MONTH_FROM_DATE, MONTH_TO_DATE), 'year': (YEAR_FROM_DATE, YEAR_TO_DATE), }[mode] QUERY = ''' SET names 'utf8'; SELECT CONCAT(firstname, ';', lastname, ';', email, ';', last_renewed_on) FROM foundationmembers WHERE last_renewed_on >= DATE_SUB( %(mysql_from_date)s , INTERVAL 2 YEAR ) AND last_renewed_on <= DATE_SUB( %(mysql_to_date)s , INTERVAL 2 YEAR ) ORDER BY last_renewed_on; '''.strip() QUERY %= {'mysql_from_date': mysql_from_date, 'mysql_to_date': mysql_to_date} infile = execute_query(QUERY) memberlist = [Member.from_csv(line.strip()) for line in infile] return memberlist def get_members_election_token(election_id): ''' Execute a SQL query to get a list of members with their temporary election token. The token need to be created first, i.e. using smth like INSERT INTO election_tmp_tokens (election_id, member_id, tmp_token) SELECT @election_id, id, SUBSTRING(MD5(RAND()) FROM 1 FOR 24) AS tmp_token FROM electorate; ''' QUERY = ''' SET names 'utf8'; SELECT CONCAT(firstname, ';', lastname, ';', email, ';', tmp_token) FROM election_tmp_tokens, foundationmembers WHERE election_id = %(election_id)s AND member_id = id; '''.strip() QUERY %= {'election_id': election_id} result = execute_query(QUERY) memberlist = [Member.from_csv(line.strip()) for line in result] return memberlist if __name__ == "__main__": parser = OptionParser() parser.add_option("-l", "--loglevel", dest="loglevel", help="Sets the loglevel to one of debug, info, warn, " "error, critical", default="info") parser.add_option("-m", "--mode", dest="mode", help="Which members to get: one of year, month, day, election" " [default: %default]", default="month") parser.add_option("-s", "--send-mail", dest="sendmail", help="Do indeed send mail [default: %default]", action="store_true", default=False) parser.add_option("-t", "--token", dest="token_for_election", help="Process temporary token, instead of renewals." "Please give the election id, which you should know, as " "argument to this parameter. A program argument is also required" " to read the instructions as a template." "[default: %default]", default=False) parser.add_option("-1", "--one-only", dest="oneonlyaddress", help="Send one mail only to this address [default: %default]", default=None) parser.add_option("-f", "--from-address", dest="fromaddress", help="Use that as sending address [default: %default]", default="Tobias Mueller ") (options, args) = parser.parse_args() loglevel = {'debug': logging.DEBUG, 'info': logging.INFO, 'warn': logging.WARN, 'error': logging.ERROR, 'critical': logging.CRITICAL}.get(options.loglevel, "warn") LOGFORMAT = "%(asctime)s %(levelname)-8s %(name)s %(message)s" DATEFORMAT = '%Y-%m-%d %H:%M:%S' logging.basicConfig(level=loglevel, format=LOGFORMAT, datefmt=DATEFORMAT) log = logging.getLogger('main') if not options.token_for_election: # This is the default. We care about renewals email_subject = 'Your GNOME Foundation Membership is about to expire' logmsg = 'Needs Renewal: %s, %s, %s, %s' template = TEMPLATE members = get_members_which_need_renewal(options.mode) else: # This is with -t option. We process election tokens. email_subject = 'GNOME Foundation Board of Directors Elections %d - Voting Instructions' % datetime.date.today().year logmsg = 'Sending Token: %s, %s, %s, %s' template = open(args[0], "r").read() members = get_members_election_token(options.token_for_election) if options.oneonlyaddress: members = [Member("firstname", "lastname", options.oneonlyaddress, "2000-05-23")] for member in members: firstname, lastname, email, token_or_last_renewed_on = member.firstname, member.lastname, member.email, member.token_or_last_renewed_on log.warn(logmsg, lastname, firstname, email, token_or_last_renewed_on) emailtext = template % {'firstname':firstname, 'lastname':lastname, 'email': email, 'token_or_last_renewed_on': token_or_last_renewed_on, 'token': token_or_last_renewed_on, 'last_renewed_on': token_or_last_renewed_on, } log.debug('The email to be sent is: %s', emailtext) to = email fromaddress = options.fromaddress subject = email_subject try: if options.sendmail or options.oneonlyaddress: send_email(to, subject, emailtext, fromaddress) except smtplib.SMTPException,e : log.error('Error sending to %s: %s', to, e) try: tfile = tempfile.NamedTemporaryFile(delete=False) except TypeError: tfile = tempfile.NamedTemporaryFile() tfile.writeline('To: %s' % to) tfile.writeline('Subject: %s' % subject) tfile.writeline('') tfile.writeline('%s' % emailtext) log.critical('eMail put in %s', tfile.name)