summary | shortlog | log | commit | commitdiff | tree
raw | patch | inline | side by side (parent: 769fef8)
raw | patch | inline | side by side (parent: 769fef8)
author | richard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2> | |
Tue, 25 Feb 2003 10:19:32 +0000 (10:19 +0000) | ||
committer | richard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2> | |
Tue, 25 Feb 2003 10:19:32 +0000 (10:19 +0000) |
address supplied in the registration form
- fixed sf bug 687771 too (now handle all cases of @/:)
git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@1544 57a73879-2fb5-44c3-a270-3262357dd7e2
- fixed sf bug 687771 too (now handle all cases of @/:)
git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@1544 57a73879-2fb5-44c3-a270-3262357dd7e2
diff --git a/CHANGES.txt b/CHANGES.txt
index eaacd1911b716790b0cae3d34fef937546844a36..2e9c214aef5e9f4a3a61476d4dcf77a1c9a6627d 100644 (file)
--- a/CHANGES.txt
+++ b/CHANGES.txt
- re-worked detectors initialisation - woohoo, no more cross-importing!
- can now configure CC to author only for messages creating issues (sf
feature 625808)
+- registration is now a two-step process, with confirmation from the email
+ address supplied in the registration form
2003-??-?? 0.5.6
diff --git a/TODO.txt b/TODO.txt
index 618d7300b7789f032c87023d210a7c7887153daa..3b995f2095a25682d9a43d5792810bff8e59930b 100644 (file)
--- a/TODO.txt
+++ b/TODO.txt
line lets us down
pending mailgw Allow different brackets delimiting [issueNNN] in Subject
pending email email sig could use a "remove me from this list"
+ <URL>/<designator>?:remove:nosy=me
pending project switch to a Roundup instance for Roundup bug/feature tracking
pending security authenticate over a secure connection
pending security optionally auth with Basic HTTP auth instead of cookies
diff --git a/roundup/__init__.py b/roundup/__init__.py
index e46226e61f87009c71e2687f09b93a94b0063ec7..e7d2da0fa178abae3d28d5421b893d31f6d7f206 100644 (file)
--- a/roundup/__init__.py
+++ b/roundup/__init__.py
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
#
-# $Id: __init__.py,v 1.19 2003-02-20 07:11:39 richard Exp $
+# $Id: __init__.py,v 1.20 2003-02-25 10:19:31 richard Exp $
''' Roundup - issue tracking for knowledge workers.
much prettier cake :)
'''
-__version__ = '0.6.0'
+__version__ = '0.6.0pr1'
# vim: set filetype=python ts=4 sw=4 et si
diff --git a/roundup/admin.py b/roundup/admin.py
index e13ee775f5df944d10ad30cee6562822cd26f1a8..94e38462d4eb60dda9283f68ecf66d4893fdef73 100644 (file)
--- a/roundup/admin.py
+++ b/roundup/admin.py
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
#
-# $Id: admin.py,v 1.37 2003-02-15 23:19:01 kedder Exp $
+# $Id: admin.py,v 1.38 2003-02-25 10:19:31 richard Exp $
'''Administration commands for maintaining Roundup trackers.
'''
# XXX perform a unit test based on the user's selections
# install!
- init.install(tracker_home, template, backend)
+ init.install(tracker_home, template)
+ init.write_select_db(tracker_home, backend)
print _('''
You should now edit the tracker configuration file:
index ab7c06ac9cd0cbf39f7478e1e653b6bf1de103c1..71ffdb038f783f9ab94ca084038398ef43192bc9 100644 (file)
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
#
-#$Id: back_anydbm.py,v 1.104 2003-02-18 01:57:38 richard Exp $
+#$Id: back_anydbm.py,v 1.105 2003-02-25 10:19:31 richard Exp $
'''
This module defines a backend that saves the hyperdatabase in a database
chosen by anydbm. It is guaranteed to always be available in python
import whichdb, anydbm, os, marshal, re, weakref, string, copy
from roundup import hyperdb, date, password, roundupdb, security
from blobfiles import FileStorage
-from sessions import Sessions
+from sessions import Sessions, OneTimeKeys
from roundup.indexer import Indexer
from roundup.backends import locking
from roundup.hyperdb import String, Password, Date, Interval, Link, \
self.transactions = []
self.indexer = Indexer(self.dir)
self.sessions = Sessions(self.config)
+ self.otks = OneTimeKeys(self.config)
self.security = security.Security(self)
# ensure files are group readable and writable
os.umask(0002)
index 91a59343127189cc2ad17c778a286f3a5cccb4d0..234a817cec77f9511bfb18338a7e2faa9c4aa2f6 100755 (executable)
'''
from roundup import hyperdb, date, password, roundupdb, security
import metakit
-from sessions import Sessions
+from sessions import Sessions, OneTimeKeys
import re, marshal, os, sys, weakref, time, calendar
from roundup import indexer
import locking
self._db = self.__open()
self.indexer = Indexer(self.config.DATABASE, self._db)
self.sessions = Sessions(self.config)
+ self.otks = OneTimeKeys(self.config)
self.security = security.Security(self)
os.umask(0002)
index e663a19207a942658210323826095888822f0f33..5b6aab436c20d1714c6ebaa583f34eb0cc992340 100644 (file)
-# $Id: rdbms_common.py,v 1.34 2003-02-18 01:57:39 richard Exp $
+# $Id: rdbms_common.py,v 1.35 2003-02-25 10:19:32 richard Exp $
''' Relational database (SQL) backend common code.
Basics:
# support
from blobfiles import FileStorage
from roundup.indexer import Indexer
-from sessions import Sessions
+from sessions import Sessions, OneTimeKeys
# number of rows to keep in memory
ROW_CACHE_SIZE = 100
self.classes = {}
self.indexer = Indexer(self.dir)
self.sessions = Sessions(self.config)
+ self.otks = OneTimeKeys(self.config)
self.security = security.Security(self)
# additional transaction support for external files and the like
index 31c48c97d87244089bd6b4a91ddd6fef7d511500..9c5325a01445b184b23b5bb9bdc7a2ab1710d957 100644 (file)
-#$Id: sessions.py,v 1.3 2002-09-10 00:11:50 richard Exp $
+#$Id: sessions.py,v 1.4 2003-02-25 10:19:32 richard Exp $
'''
This module defines a very basic store that's used by the CGI interface
-to store session information.
+to store session and one-time-key information.
+
+Yes, it's called "sessions" - because originally it only defined a session
+class. It's now also used for One Time Key handling too.
+
'''
import anydbm, whichdb, os, marshal
-class Sessions:
- ''' Back onto an anydbm store.
+class BasicDatabase:
+ ''' Provide a nice encapsulation of an anydbm store.
- Keys are session id strings, values are marshalled data.
+ Keys are id strings, values are automatically marshalled data.
'''
def __init__(self, config):
self.config = config
os.umask(0002)
def clear(self):
- path = os.path.join(self.dir, 'sessions')
+ path = os.path.join(self.dir, self.name)
if os.path.exists(path):
os.remove(path)
elif os.path.exists(path+'.db'): # dbm appends .db
db_type = 'dbm'
return db_type
- def get(self, sessionid, value):
+ def get(self, infoid, value):
db = self.opendb('c')
try:
- if db.has_key(sessionid):
- values = marshal.loads(db[sessionid])
+ if db.has_key(infoid):
+ values = marshal.loads(db[infoid])
else:
return None
return values.get(value, None)
finally:
db.close()
- def set(self, sessionid, **newvalues):
+ def getall(self, infoid):
+ db = self.opendb('c')
+ try:
+ return marshal.loads(db[infoid])
+ finally:
+ db.close()
+
+ def set(self, infoid, **newvalues):
db = self.opendb('c')
try:
- if db.has_key(sessionid):
- values = marshal.loads(db[sessionid])
+ if db.has_key(infoid):
+ values = marshal.loads(db[infoid])
else:
values = {}
values.update(newvalues)
- db[sessionid] = marshal.dumps(values)
+ db[infoid] = marshal.dumps(values)
finally:
db.close()
finally:
db.close()
- def destroy(self, sessionid):
+ def destroy(self, infoid):
db = self.opendb('c')
try:
- if db.has_key(sessionid):
- del db[sessionid]
+ if db.has_key(infoid):
+ del db[infoid]
finally:
db.close()
eccentricities.
'''
# figure the class db type
- path = os.path.join(os.getcwd(), self.dir, 'sessions')
+ path = os.path.join(os.getcwd(), self.dir, self.name)
db_type = self.determine_db_type(path)
# new database? let anydbm pick the best dbm
def commit(self):
pass
+
+class Sessions(BasicDatabase):
+ name = 'sessions'
+
+class OneTimeKeys(BasicDatabase):
+ name = 'otks'
+
diff --git a/roundup/cgi/client.py b/roundup/cgi/client.py
index 1f23faea242d87b281c686f1b7db7b4d5eda0b2e..435f7334947f37ed4e56ef5b651d5660864e20e2 100644 (file)
--- a/roundup/cgi/client.py
+++ b/roundup/cgi/client.py
-# $Id: client.py,v 1.96 2003-02-20 07:13:14 richard Exp $
+# $Id: client.py,v 1.97 2003-02-25 10:19:32 richard Exp $
__doc__ = """
WWW request handler (also used in the stand-alone server).
"""
import os, os.path, cgi, StringIO, urlparse, re, traceback, mimetypes, urllib
-import binascii, Cookie, time, random
+import binascii, Cookie, time, random, MimeWriter, smtplib, socket, quopri
from roundup import roundupdb, date, hyperdb, password
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
class HTTPException(Exception):
pass
FV_OK_MESSAGE = re.compile(r'[@:]ok_message')
FV_ERROR_MESSAGE = re.compile(r'[@:]error_message')
+ FV_QUERYNAME = re.compile(r'[@:]queryname')
+
# edit form variable handling (see unit tests)
FV_LABELS = r'''
^(
except SendStaticFile, file:
self.serve_static_file(str(file))
except Unauthorised, message:
- self.classname=None
- self.template=''
+ self.classname = None
+ self.template = ''
self.error_message.append(message)
self.write(self.renderContext())
except NotFound:
sessions = self.db.sessions
# look up the user session cookie
- cookie = Cookie.Cookie(self.env.get('HTTP_COOKIE', ''))
+ cookie = Cookie.SimpleCookie(self.env.get('HTTP_COOKIE', ''))
user = 'anonymous'
# bump the "revision" of the cookie since the format changed
('editCSV', 'editCSVAction'),
('new', 'newItemAction'),
('register', 'registerAction'),
+ ('confrego', 'confRegoAction'),
('login', 'loginAction'),
('logout', 'logout_action'),
('search', 'searchAction'),
('show', 'showAction'),
)
def handle_action(self):
- ''' Determine whether there should be an _action called.
+ ''' Determine whether there should be an Action called.
The action is defined by the form variable :action which
identifies the method on this object to call. The four basic
"edit" -> self.editItemAction
"new" -> self.newItemAction
"register" -> self.registerAction
+ "confrego" -> self.confRegoAction
"login" -> self.loginAction
"logout" -> self.logout_action
"search" -> self.searchAction
"retire" -> self.retireAction
'''
- if not self.form.has_key(':action'):
+ if self.form.has_key(':action'):
+ action = self.form[':action'].value.lower()
+ elif self.form.has_key('@action'):
+ action = self.form['@action'].value.lower()
+ else:
return None
try:
# get the action, validate it
- action = self.form[':action'].value
for name, method in self.actions:
if name == action:
break
else:
raise ValueError, 'No such action "%s"'%action
-
# call the mapped action
getattr(self, method)()
except Redirect:
raise
except Unauthorised:
raise
- except:
- self.db.rollback()
- s = StringIO.StringIO()
- traceback.print_exc(None, s)
- self.error_message.append('<pre>%s</pre>'%cgi.escape(s.getvalue()))
def write(self, content):
if not self.headers_done:
# Let the user know what's going on
self.ok_message.append(_('You are logged out'))
+ chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
def registerAction(self):
'''Attempt to create a new user based on the contents of the form
and then set the cookie.
return 1 on successful login
'''
- # create the new user
- cl = self.db.user
-
# parse the props from the form
try:
- props = self.parsePropsFromForm()
+ props = self.parsePropsFromForm()[0][('user', None)]
except (ValueError, KeyError), message:
self.error_message.append(_('Error: ') + str(message))
return
if not self.registerPermission(props):
raise Unauthorised, _("You do not have permission to register")
+ try:
+ self.db.user.lookup(props['username'])
+ self.error_message.append('Error: A user with the username "%s" '
+ 'already exists'%props['username'])
+ return
+ except KeyError:
+ pass
+
+ # generate the one-time-key and store the props for later
+ otk = ''.join([random.choice(self.chars) for x in range(32)])
+ 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] = str(value)
+ elif isinstance(proptype, hyperdb.Interval):
+ props[propname] = str(value)
+ elif isinstance(proptype, hyperdb.Password):
+ props[propname] = str(value)
+ self.db.otks.set(otk, **props)
+
+ # send email to the user's email address
+ message = StringIO.StringIO()
+ writer = MimeWriter.MimeWriter(message)
+ tracker_name = self.db.config.TRACKER_NAME
+ s = 'Complete your registration to %s'%tracker_name
+ writer.addheader('Subject', encode_header(s))
+ writer.addheader('To', props['address'])
+ 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('''
+To complete your registration of the user "%(name)s" with %(tracker)s,
+please visit the following URL:
+
+ http://localhost:8001/test/?@action=confrego&otk=%(otk)s
+'''%{'name': props['username'], 'tracker': tracker_name, 'url': self.base,
+ 'otk': otk})
+ quopri.encode(content, body, 0)
+
+ # now try to send the message
+ try:
+ # send the message as admin so bounces are sent there
+ # instead of to roundup
+ smtp = smtplib.SMTP(self.db.config.MAILHOST)
+ smtp.sendmail(self.db.config.ADMIN_EMAIL, [props['address']],
+ message.getvalue())
+ except socket.error, value:
+ self.error_message.append("Error: couldn't send "
+ "confirmation email: mailhost %s"%value)
+ return
+ except smtplib.SMTPException, value:
+ self.error_message.append("Error: couldn't send "
+ "confirmation email: %s"%value)
+ return
+
+ # commit changes to the database
+ self.db.commit()
+
+ # redirect to the "you're almost there" page
+ raise Redirect, '%s?:template=rego_step1_done'%self.base
+
+ def registerPermission(self, props):
+ ''' Determine whether the user has permission to register
+
+ Base behaviour is to check the user has "Web Registration".
+ '''
+ # registration isn't allowed to supply roles
+ if props.has_key('roles'):
+ return 0
+ if self.db.security.hasPermission('Web Registration', self.userid):
+ return 1
+ return 0
+
+ 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
- try:
+# XXX we need to make the "default" page be able to display errors!
+# try:
+ if 1:
props['roles'] = self.instance.config.NEW_WEB_USER_ROLES
- self.userid = cl.create(**props['user'])
+ self.userid = cl.create(**props)
+ # clear the props from the otk database
+ self.db.otks.destroy(otk)
self.db.commit()
- except (ValueError, KeyError), message:
- self.error_message.append(message)
- return
+# except (ValueError, KeyError), message:
+# self.error_message.append(str(message))
+# return
# log the new user in
self.user = cl.get(self.userid, 'username')
message = _('You are now registered, welcome!')
# redirect to the item's edit page
- raise Redirect, '%s%s%s?+ok_message=%s'%(
- self.base, self.classname, self.userid, urllib.quote(message))
-
- def registerPermission(self, props):
- ''' Determine whether the user has permission to register
-
- Base behaviour is to check the user has "Web Registration".
- '''
- # registration isn't allowed to supply roles
- if props.has_key('roles'):
- return 0
- if self.db.security.hasPermission('Web Registration', self.userid):
- return 1
- return 0
+ raise Redirect, '%suser%s?@ok_message=%s'%(
+ self.base, self.userid, urllib.quote(message))
def editItemAction(self):
''' Perform an edit of an item in the database.
See parsePropsFromForm and _editnodes for special variables
'''
# parse the props from the form
- if 1:
+# XXX reinstate exception handling
# try:
+ if 1:
props, links = self.parsePropsFromForm()
# except (ValueError, KeyError), message:
# self.error_message.append(_('Error: ') + str(message))
# return
# handle the props
- if 1:
+# XXX reinstate exception handling
# try:
+ if 1:
message = self._editnodes(props, links)
# except (ValueError, KeyError, IndexError), message:
# self.error_message.append(_('Error: ') + str(message))
''' Use the props in all_props to perform edit and creation, then
use the link specs in all_links to do linking.
'''
-# print '='*75
-# print 'ALL_PROPS', all_props
# figure dependencies and re-work links
deps = {}
links = {}
deps.setdefault((cn, nodeid), []).append(value)
links.setdefault(value, []).append((cn, nodeid, propname))
-# print '*'*75
-# print 'LINKS', links
-# print 'DEPS', deps
-
# figure chained dependencies ordering
order = []
done = {}
if done.has_key(needed):
continue
tlist = deps.get(needed, [])
-# print 'SOLVING', needed, tlist
for target in tlist:
if not done.has_key(target):
break
else:
-# print 'DONE', needed
done[needed] = 1
order.append(needed)
change = 1
_('You do not have permission to search %s' %self.classname))
# add a faked :filter form variable for each filtering prop
-# XXX migrate to new : @ +
props = self.db.classes[self.classname].getprops()
+ queryname = ''
for key in self.form.keys():
- if not props.has_key(key): continue
+ # special vars
+ if self.FV_QUERYNAME.match(key):
+ queryname = self.form[key].value.strip()
+ continue
+
+ if not props.has_key(key):
+ continue
if isinstance(self.form[key], type([])):
# search for at least one entry which is not empty
for minifield in self.form[key]:
continue
else:
if not self.form[key].value: continue
- self.form.value.append(cgi.MiniFieldStorage(':filter', key))
+ self.form.value.append(cgi.MiniFieldStorage('@filter', key))
# handle saving the query params
- if self.form.has_key(':queryname'):
- queryname = self.form[':queryname'].value.strip()
- if queryname:
- # parse the environment and figure what the query _is_
- req = HTMLRequest(self)
- url = req.indexargs_href('', {})
-
- # handle editing an existing query
- try:
- qid = self.db.query.lookup(queryname)
- self.db.query.set(qid, klass=self.classname, url=url)
- except KeyError:
- # create a query
- qid = self.db.query.create(name=queryname,
- klass=self.classname, url=url)
+ if queryname:
+ # parse the environment and figure what the query _is_
+ req = HTMLRequest(self)
+ url = req.indexargs_href('', {})
+
+ # handle editing an existing query
+ try:
+ qid = self.db.query.lookup(queryname)
+ self.db.query.set(qid, klass=self.classname, url=url)
+ except KeyError:
+ # create a query
+ qid = self.db.query.create(name=queryname,
+ klass=self.classname, url=url)
- # and add it to the user's query multilink
- queries = self.db.user.get(self.userid, 'queries')
- queries.append(qid)
- self.db.user.set(self.userid, queries=queries)
+ # and add it to the user's query multilink
+ queries = self.db.user.get(self.userid, 'queries')
+ queries.append(qid)
+ self.db.user.set(self.userid, queries=queries)
- # commit the query change to the database
- self.db.commit()
+ # commit the query change to the database
+ self.db.commit()
def searchPermission(self):
''' Determine whether the user has permission to search this class.
return 1
- def showAction(self):
- ''' Show a node
+ def showAction(self, typere=re.compile('[@:]type'),
+ numre=re.compile('[@:]number')):
+ ''' Show a node of a particular class/id
'''
-# XXX allow : @ +
- t = self.form[':type'].value
- n = self.form[':number'].value
+ t = n = ''
+ for key in self.form.keys():
+ if typere.match(key):
+ t = self.form[key].value.strip()
+ elif numre.match(key):
+ n = self.form[key].value.strip()
+ if not t:
+ raise ValueError, 'Invalid %s number'%t
url = '%s%s%s'%(self.db.config.TRACKER_WEB, t, n)
raise Redirect, url
index 3e174c1fdde2a5abe921a0cedacbf66f6d54a410..dff1bbcbe405b5cdb409b5e20ddc7d88918a0756 100644 (file)
return '\n'.join(l)
def indexargs_url(self, url, args):
- ''' embed the current index args in a URL '''
+ ''' Embed the current index args in a URL
+ '''
sc = self.special_char
l = ['%s=%s'%(k,v) for k,v in args.items()]
- if self.columns and not args.has_key(':columns'):
+
+ # pull out the special values (prefixed by @ or :)
+ specials = {}
+ for key in args.keys():
+ if key[0] in '@:':
+ specials[key[1:]] = args[key]
+
+ # ok, now handle the specials we received in the request
+ if self.columns and not specials.has_key('columns'):
l.append(sc+'columns=%s'%(','.join(self.columns)))
- if self.sort[1] is not None and not args.has_key(':sort'):
+ if self.sort[1] is not None and not specials.has_key('sort'):
if self.sort[0] == '-':
val = '-'+self.sort[1]
else:
val = self.sort[1]
l.append(sc+'sort=%s'%val)
- if self.group[1] is not None and not args.has_key(':group'):
+ if self.group[1] is not None and not specials.has_key('group'):
if self.group[0] == '-':
val = '-'+self.group[1]
else:
val = self.group[1]
l.append(sc+'group=%s'%val)
- if self.filter and not args.has_key(':filter'):
+ if self.filter and not specials.has_key('filter'):
l.append(sc+'filter=%s'%(','.join(self.filter)))
+ if self.search_text and not specials.has_key('search_text'):
+ l.append(sc+'search_text=%s'%self.search_text)
+ if not specials.has_key('pagesize'):
+ l.append(sc+'pagesize=%s'%self.pagesize)
+ if not specials.has_key('startwith'):
+ l.append(sc+'startwith=%s'%self.startwith)
+
+ # finally, the remainder of the filter args in the request
for k,v in self.filterspec.items():
if not args.has_key(k):
if type(v) == type([]):
l.append('%s=%s'%(k, ','.join(v)))
else:
l.append('%s=%s'%(k, v))
- if self.search_text and not args.has_key(':search_text'):
- l.append(sc+'search_text=%s'%self.search_text)
- if not args.has_key(':pagesize'):
- l.append(sc+'pagesize=%s'%self.pagesize)
- if not args.has_key(':startwith'):
- l.append(sc+'startwith=%s'%self.startwith)
return '%s?%s'%(url, '&'.join(l))
indexargs_href = indexargs_url
diff --git a/roundup/init.py b/roundup/init.py
index b0e2b5b8bc03f0e38078dc3276f84f76e3541347..9a7ffc51241d5c8bb94459809671d1963bee99fe 100644 (file)
--- a/roundup/init.py
+++ b/roundup/init.py
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
#
-# $Id: init.py,v 1.24 2002-09-10 12:44:42 richard Exp $
+# $Id: init.py,v 1.25 2003-02-25 10:19:31 richard Exp $
__doc__ = """
Init (create) a roundup instance.
links are copied.
This was copied from shutil.py in std lib.
-
"""
names = os.listdir(src)
try:
else:
install_util.copyDigestedFile(srcname, dstname)
-def install(instance_home, template, backend):
+def install(instance_home, template):
'''Install an instance using the named template and backend.
instance_home - the directory to place the instance data in
builder.installHtmlBase(template_name, instance_home)
+
+def write_select_db(instance_home, backend):
+ ''' Write the file that selects the backend for the tracker
+ '''
# now select database
db = '''# WARNING: DO NOT EDIT THIS FILE!!!
from roundup.backends.back_%s import Database, Class, FileClass, IssueClass