diff --git a/roundup/cgi/client.py b/roundup/cgi/client.py
index 7b7d86cf9fea1a40b25976e2d93dd07151dbcad1..306a3798e266d2e96851c33603366c6d143392c1 100644 (file)
--- a/roundup/cgi/client.py
+++ b/roundup/cgi/client.py
-# $Id: client.py,v 1.125 2003-07-03 23:43:47 richard Exp $
+# $Id: client.py,v 1.138 2003-09-08 21:08:18 jlgijsbers Exp $
__doc__ = """
WWW request handler (also used in the stand-alone server).
import binascii, Cookie, time, random, MimeWriter, smtplib, socket, quopri
import stat, rfc822, string
-from roundup import roundupdb, date, hyperdb, password, token
+from roundup import roundupdb, date, hyperdb, password, token, rcsv
from roundup.i18n import _
from roundup.cgi.templating import Templates, HTMLRequest, NoTemplate
from roundup.cgi import cgitb
from roundup.cgi.PageTemplates import PageTemplate
from roundup.rfc2822 import encode_header
-from roundup.mailgw import uidFromAddress, openSMTPConnection
+from roundup.mailgw import uidFromAddress
+from roundup.mailer import Mailer, MessageSendError
class HTTPException(Exception):
pass
class NotModified(HTTPException):
pass
-# set to indicate to roundup not to actually _send_ email
-# this var must contain a file to write the mail to
-SENDMAILDEBUG = os.environ.get('SENDMAILDEBUG', '')
-
# used by a couple of routines
-chars = string.letters+string.digits
+if hasattr(string, 'ascii_letters'):
+ chars = string.ascii_letters+string.digits
+else:
+ # python2.1 doesn't have ascii_letters
+ chars = string.letters+string.digits
# XXX actually _use_ FormError
class FormError(ValueError):
self.instance = instance
self.request = request
self.env = env
+ self.mailer = Mailer(instance.config)
# save off the path
self.path = env['PATH_INFO']
def determine_user(self):
''' Determine who the user is
'''
+ # open the database as admin
+ self.opendb('admin')
+
# clean age sessions
self.clean_sessions()
raise NotFound, designator
# we just want to serve up the file named
+ self.opendb('admin')
file = self.db.file
self.additional_headers['Content-Type'] = file.get(nodeid, 'type')
self.write(file.get(nodeid, 'content'))
# send the email
tracker_name = self.db.config.TRACKER_NAME
- subject = 'Complete your registration to %s'%tracker_name
- body = '''
-To complete your registration of the user "%(name)s" with %(tracker)s,
-please visit the following URL:
+ tracker_email = self.db.config.TRACKER_EMAIL
+ subject = 'Complete your registration to %s -- key %s' % (tracker_name,
+ otk)
+ body = """To complete your registration of the user "%(name)s" with
+%(tracker)s, please do one of the following:
+
+- send a reply to %(tracker_email)s and maintain the subject line as is (the
+reply's additional "Re:" is ok),
+
+- or visit the following URL:
%(url)s?@action=confrego&otk=%(otk)s
-'''%{'name': props['username'], 'tracker': tracker_name, 'url': self.base,
- 'otk': otk}
- if not self.sendEmail(props['address'], subject, body):
+""" % {'name': props['username'], 'tracker': tracker_name, 'url': self.base,
+ 'otk': otk, 'tracker_email': tracker_email}
+ if not self.standard_message(props['address'], subject, body,
+ tracker_email):
return
# commit changes to the database
# redirect to the "you're almost there" page
raise Redirect, '%suser?@template=rego_progress'%self.base
- def sendEmail(self, to, subject, content):
- # send email to the user's email address
- message = StringIO.StringIO()
- writer = MimeWriter.MimeWriter(message)
- tracker_name = self.db.config.TRACKER_NAME
- writer.addheader('Subject', encode_header(subject))
- writer.addheader('To', to)
- writer.addheader('From', roundupdb.straddr((tracker_name,
- self.db.config.ADMIN_EMAIL)))
- writer.addheader('Date', time.strftime("%a, %d %b %Y %H:%M:%S +0000",
- time.gmtime()))
- # add a uniquely Roundup header to help filtering
- writer.addheader('X-Roundup-Name', tracker_name)
- # avoid email loops
- writer.addheader('X-Roundup-Loop', 'hello')
- writer.addheader('Content-Transfer-Encoding', 'quoted-printable')
- body = writer.startbody('text/plain; charset=utf-8')
-
- # message body, encoded quoted-printable
- content = StringIO.StringIO(content)
- quopri.encode(content, body, 0)
-
- if SENDMAILDEBUG:
- # don't send - just write to a file
- open(SENDMAILDEBUG, 'a').write('FROM: %s\nTO: %s\n%s\n'%(
- self.db.config.ADMIN_EMAIL,
- ', '.join(to),message.getvalue()))
- else:
- # now try to send the message
- try:
- # send the message as admin so bounces are sent there
- # instead of to roundup
- smtp = openSMTPConnection(self.db.config)
- smtp.sendmail(self.db.config.ADMIN_EMAIL, [to],
- message.getvalue())
- except socket.error, value:
- self.error_message.append("Error: couldn't send email: "
- "mailhost %s"%value)
- return 0
- except smtplib.SMTPException, msg:
- self.error_message.append("Error: couldn't send email: %s"%msg)
- return 0
- return 1
-
+ def standard_message(self, to, subject, body, author=None):
+ try:
+ self.mailer.standard_message(to, subject, body, author)
+ return 1
+ except MessageSendError, e:
+ self.error_message.append(str(e))
+
def registerPermission(self, props):
''' Determine whether the user has permission to register
def confRegoAction(self):
''' Grab the OTK, use it to load up the new user details
'''
- # pull the rego information out of the otk database
- otk = self.form['otk'].value
- props = self.db.otks.getall(otk)
- for propname, proptype in self.db.user.getprops().items():
- value = props.get(propname, None)
- if value is None:
- pass
- elif isinstance(proptype, hyperdb.Date):
- props[propname] = date.Date(value)
- elif isinstance(proptype, hyperdb.Interval):
- props[propname] = date.Interval(value)
- elif isinstance(proptype, hyperdb.Password):
- props[propname] = password.Password()
- props[propname].unpack(value)
-
- # re-open the database as "admin"
- if self.user != 'admin':
- self.opendb('admin')
-
- # create the new user
- cl = self.db.user
-# XXX we need to make the "default" page be able to display errors!
try:
- props['roles'] = self.instance.config.NEW_WEB_USER_ROLES
- del props['__time']
- self.userid = cl.create(**props)
- # clear the props from the otk database
- self.db.otks.destroy(otk)
- self.db.commit()
+ # pull the rego information out of the otk database
+ self.userid = self.db.confirm_registration(self.form['otk'].value)
except (ValueError, KeyError), message:
+ # XXX: we need to make the "default" page be able to display errors!
self.error_message.append(str(message))
return
-
+
# log the new user in
- self.user = cl.get(self.userid, 'username')
+ self.user = self.db.user.get(self.userid, 'username')
# re-open the database for real, using the user
self.opendb(self.user)
Your password is now: %(password)s
'''%{'name': name, 'password': newpw}
- if not self.sendEmail(address, subject, body):
+ if not self.standard_message(address, subject, body):
return
self.ok_message.append('Password reset and email sent to %s'%address)
You should then receive another email with the new password.
'''%{'name': name, 'tracker': tracker_name, 'url': self.base, 'otk': otk}
- if not self.sendEmail(address, subject, body):
+ if not self.standard_message(address, subject, body):
return
self.ok_message.append('Email sent to %s'%address)
try:
props, links = self.parsePropsFromForm()
except (ValueError, KeyError), message:
- self.error_message.append(_('Error: ') + str(message))
+ self.error_message.append(_('Parse Error: ') + str(message))
return
# handle the props
try:
message = self._editnodes(props, links)
except (ValueError, KeyError, IndexError), message:
- self.error_message.append(_('Error: ') + str(message))
+ self.error_message.append(_('Apply Error: ') + str(message))
return
# commit now that all the tricky stuff is done
_('You do not have permission to edit %s' %self.classname))
# get the CSV module
- try:
- import csv
- except ImportError:
- self.error_message.append(_(
- 'Sorry, you need the csv module to use this function.<br>\n'
- 'Get it from: <a href="http://www.object-craft.com.au/projects/csv/">http://www.object-craft.com.au/projects/csv/'))
+ if rcsv.error:
+ self.error_message.append(_(rcsv.error))
return
cl = self.db.classes[self.classname]
props = ['id'] + idlessprops
# do the edit
- rows = self.form['rows'].value.splitlines()
- p = csv.parser()
+ rows = StringIO.StringIO(self.form['rows'].value)
+ reader = rcsv.reader(rows, rcsv.comma_separated)
found = {}
line = 0
- for row in rows[1:]:
+ for values in reader:
line += 1
- values = p.parse(row)
- # not a complete row, keep going
- if not values: continue
-
+ if line == 1: continue
# skip property names header
if values == props:
continue
found[nodeid] = 1
# see if the node exists
- if cl.hasnode(nodeid):
- exists = 1
- else:
+ if nodeid in ('x', 'X') or not cl.hasnode(nodeid):
exists = 0
+ else:
+ exists = 1
# confirm correct weight
if len(idlessprops) != len(values):
# if it's a multilink, split it
if isinstance(prop, hyperdb.Multilink):
value = value.split(':')
+ elif isinstance(prop, hyperdb.Password):
+ value = password.Password(value)
+ elif isinstance(prop, hyperdb.Interval):
+ value = date.Interval(value)
+ elif isinstance(prop, hyperdb.Date):
+ value = date.Date(value)
+ elif isinstance(prop, hyperdb.Boolean):
+ value = value.lower() in ('yes', 'true', 'on', '1')
+ elif isinstance(prop, hyperdb.Number):
+ value = float(value)
d[name] = value
elif exists:
# nuke the existing value
if queryname:
# parse the environment and figure what the query _is_
req = HTMLRequest(self)
- url = req.indexargs_href('', {})
+
+ # The [1:] strips off the '?' character, it isn't part of the
+ # query string.
+ url = req.indexargs_href('', {})[1:]
# handle editing an existing query
try:
if s:
raise ValueError, '\n'.join(s)
- # check that FileClass entries have a "content" property with
- # content, otherwise remove them
+ # When creating a FileClass node, it should have a non-empty content
+ # property to be created. When editing a FileClass node, it should
+ # either have a non-empty content property or no property at all. In
+ # the latter case, nothing will change.
for (cn, id), props in all_props.items():
- cl = self.db.classes[cn]
- if not isinstance(cl, hyperdb.FileClass):
- continue
- # we also don't want to create FileClass items with no content
- if not props.get('content', ''):
- del all_props[(cn, id)]
+ if isinstance(self.db.classes[cn], hyperdb.FileClass):
+ if id == '-1':
+ if not props.get('content', ''):
+ del all_props[(cn, id)]
+ elif props.has_key('content') and not props['content']:
+ raise ValueError, _('File is empty')
return all_props, all_links
def fixNewlines(text):