summary | shortlog | log | commit | commitdiff | tree
raw | patch | inline | side by side (parent: 948234c)
raw | patch | inline | side by side (parent: 948234c)
author | richard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2> | |
Thu, 13 Feb 2003 07:38:34 +0000 (07:38 +0000) | ||
committer | richard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2> | |
Thu, 13 Feb 2003 07:38:34 +0000 (07:38 +0000) |
git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@1503 57a73879-2fb5-44c3-a270-3262357dd7e2
roundup/cgi/client.py | patch | blob | history | |
test/test_cgi.py | patch | blob | history |
diff --git a/roundup/cgi/client.py b/roundup/cgi/client.py
index 3bef4d8129c83d02bf5db88c2eb79981fa67ea5d..18da380f9d5e4218d9477d8e53bd29b79b428c73 100644 (file)
--- a/roundup/cgi/client.py
+++ b/roundup/cgi/client.py
-# $Id: client.py,v 1.81 2003-02-12 07:14:29 richard Exp $
+# $Id: client.py,v 1.82 2003-02-13 07:38:34 richard Exp $
__doc__ = """
WWW request handler (also used in the stand-alone server).
FV_ADD = re.compile(r'([@+:])add\1')
FV_REMOVE = re.compile(r'([@+:])remove\1')
FV_CONFIRM = re.compile(r'.+[@+:]confirm')
-
- # post-edi
- FV_LINK = re.compile(r'[@+:]link')
- FV_MULTILINK = re.compile(r'[@+:]multilink')
+ FV_LINK = re.compile(r'([@+:])link\1(.+)')
# deprecated
FV_NOTE = re.compile(r'[@+:]note')
if now - last_clean > hour:
# remove age sessions
for sessid in sessions.list():
- print sessid
interval = now - sessions.get(sessid, 'last_use')
if interval > week:
sessions.destroy(sessid)
def editItemAction(self):
''' Perform an edit of an item in the database.
- Some special form elements:
-
- :link=designator:property
- :multilink=designator:property
- The value specifies a node designator and the property on that
- node to add _this_ node to as a link or multilink.
- :note
- Create a message and attach it to the current node's
- "messages" property.
- :file
- Create a file and attach it to the current node's
- "files" property. Attach the file to the message created from
- the :note if it's supplied.
-
- See parsePropsFromForm for more special variables
+ See parsePropsFromForm and _editnodes for special variables
'''
# parse the props from the form
try:
- props = self.parsePropsFromForm()
+ props, links = self.parsePropsFromForm()
except (ValueError, KeyError), message:
self.error_message.append(_('Error: ') + str(message))
return
- # check permission
- if not self.editItemPermission(props):
- self.error_message.append(
- _('You do not have permission to edit %(classname)s'%
- self.__dict__))
- return
-
- # identify the entry in the props parsed from the form
- this = self.classname + self.nodeid
-
- # perform the edit
+ # handle the props
try:
- # make changes to the node
- props = self._changenode(props[this])
- # handle linked nodes
- self._post_editnode(self.nodeid)
+ message = self._editnodes(props, links)
except (ValueError, KeyError, IndexError), message:
self.error_message.append(_('Error: ') + str(message))
return
# commit now that all the tricky stuff is done
self.db.commit()
- # and some nice feedback for the user
- if props:
- message = _('%(changes)s edited ok')%{'changes':
- ', '.join(props.keys())}
- else:
- message = _('nothing changed')
-
# redirect to the item's edit page
raise Redirect, '%s%s%s?+ok_message=%s'%(self.base, self.classname,
self.nodeid, urllib.quote(message))
special form values.
'''
# parse the props from the form
- try:
- props = self.parsePropsFromForm()
- except (ValueError, KeyError), message:
- self.error_message.append(_('Error: ') + str(message))
- return
-
- if not self.newItemPermission(props):
- self.error_message.append(
- _('You do not have permission to create %s' %self.classname))
-
- # create a little extra message for anticipated :link / :multilink
- if self.form.has_key(':multilink'):
- link = self.form[':multilink'].value
- elif self.form.has_key(':link'):
- link = self.form[':multilink'].value
- else:
- link = None
- xtra = ''
- if link:
- designator, linkprop = link.split(':')
- xtra = ' for <a href="%s">%s</a>'%(designator, designator)
-
- try:
- # do the create
- nid = self._createnode(props[self.classname])
- except (ValueError, KeyError, IndexError), message:
- # these errors might just be indicative of user dumbness
- self.error_message.append(_('Error: ') + str(message))
- return
- except:
- # oops
- self.db.rollback()
- s = StringIO.StringIO()
- traceback.print_exc(None, s)
- self.error_message.append('<pre>%s</pre>'%cgi.escape(s.getvalue()))
- return
-
- try:
- # handle linked nodes
- self._post_editnode(nid)
+# try:
+ if 1:
+ props, links = self.parsePropsFromForm()
+# except (ValueError, KeyError), message:
+# self.error_message.append(_('Error: ') + str(message))
+# return
+
+ # handle the props - edit or create
+# try:
+ if 1:
+ # create the context here
+ cn = self.classname
+ nid = self._createnode(cn, props[(cn, None)])
+ del props[(cn, None)]
+
+ extra = self._editnodes(props, links, {(cn, None): nid})
+ if extra:
+ extra = '<br>' + extra
+
+ # now do the rest
+ messages = '%s %s created'%(cn, nid) + extra
+# except (ValueError, KeyError, IndexError), message:
+# # these errors might just be indicative of user dumbness
+# self.error_message.append(_('Error: ') + str(message))
+# return
- # commit now that all the tricky stuff is done
- self.db.commit()
-
- # render the newly created item
- self.nodeid = nid
-
- # and some nice feedback for the user
- message = _('%(classname)s created ok')%self.__dict__ + xtra
- except:
- # oops
- self.db.rollback()
- s = StringIO.StringIO()
- traceback.print_exc(None, s)
- self.error_message.append('<pre>%s</pre>'%cgi.escape(s.getvalue()))
- return
+ # commit now that all the tricky stuff is done
+ self.db.commit()
# redirect to the new item's page
raise Redirect, '%s%s%s?:ok_message=%s'%(self.base, self.classname,
- nid, urllib.quote(message))
+ nid, urllib.quote(messages))
def newItemPermission(self, props):
''' Determine whether the user has permission to create (edit) this
return 0
return 1
+
def retireAction(self):
''' Retire the context item.
'''
#
# Utility methods for editing
#
- def _changenode(self, props):
- ''' change the node based on the contents of the form
+ def _editnodes(self, all_props, all_links, newids=None):
+ ''' Use the props in all_props to perform edit and creation, then
+ use the link specs in all_links to do linking.
'''
- cl = self.db.classes[self.classname]
+ m = []
+ if newids is None:
+ newids = {}
+ for (cn, nodeid), props in all_props.items():
+ if int(nodeid) > 0:
+ # make changes to the node
+ props = self._changenode(cn, nodeid, props)
+
+ # and some nice feedback for the user
+ if props:
+ info = ', '.join(props.keys())
+ m.append('%s %s %s edited ok'%(cn, nodeid, info))
+ else:
+ m.append('%s %s - nothing changed'%(cn, nodeid))
+ elif props:
+ # make a new node
+ newid = self._createnode(cn, props)
+ newids[(cn, nodeid)] = newid
+ nodeid = newid
- # create the message
- message, files = self._handle_message()
- if message:
- props['messages'] = cl.get(self.nodeid, 'messages') + [message]
- if files:
- props['files'] = cl.get(self.nodeid, 'files') + files
+ # and some nice feedback for the user
+ m.append('%s %s created'%(cn, newid))
- # make the changes
- return cl.set(self.nodeid, **props)
+ # handle linked nodes
+ keys = self.form.keys()
+ for cn, nodeid, propname, value in all_links:
+ cl = self.db.classes[cn]
+ property = cl.getprops()[propname]
+ if nodeid is None or nodeid.startswith('-'):
+ if not newids.has_key((cn, nodeid)):
+ continue
+ nodeid = newids[(cn, nodeid)]
- def _createnode(self, props):
- ''' create a node based on the contents of the form
- '''
- cl = self.db.classes[self.classname]
+ # map the desired classnames to their actual created ids
+ for link in value:
+ if not newids.has_key(link):
+ continue
+ linkid = newids[link]
+ if isinstance(property, hyperdb.Multilink):
+ # take a dupe of the list so we're not changing the cache
+ existing = cl.get(nodeid, propname)[:]
+ existing.append(linkid)
+ cl.set(nodeid, **{propname: existing})
+ elif isinstance(property, hyperdb.Link):
+ # make the Link set
+ cl.set(nodeid, **{propname: linkid})
+ else:
+ raise ValueError, '%s %s is not a link or multilink '\
+ 'property'%(cn, propname)
+ m.append('%s %s linked to <a href="%s%s">%s %s</a>'%(
+ link[0], linkid, cn, nodeid, cn, nodeid))
- # check for messages and files
- message, files = self._handle_message()
- if message:
- props['messages'] = [message]
- if files:
- props['files'] = files
- # create the node and return it's id
- return cl.create(**props)
+ return '<br>'.join(m)
- def _handle_message(self):
- ''' generate an edit message
+ def _changenode(self, cn, nodeid, props):
+ ''' change the node based on the contents of the form
'''
- # handle file attachments
- files = []
- if self.form.has_key(':file'):
- file = self.form[':file']
-
- # if there's a filename, then we create a file
- if file.filename:
- # see if there are any file properties we should set
- file_props={};
- if self.form.has_key(':file_fields'):
- for field in self.form[':file_fields'].value.split(','):
- if self.form.has_key(field):
- if field.startswith("file_"):
- file_props[field[5:]] = self.form[field].value
- else :
- file_props[field] = self.form[field].value
-
- # try to determine the file content-type
- filename = file.filename.split('\\')[-1]
- mime_type = mimetypes.guess_type(filename)[0]
- if not mime_type:
- mime_type = "application/octet-stream"
-
- # create the new file entry
- files.append(self.db.file.create(type=mime_type,
- name=filename, content=file.file.read(), **file_props))
-
- # we don't want to do a message if none of the following is true...
- cn = self.classname
- cl = self.db.classes[self.classname]
- props = cl.getprops()
- note = None
- # in a nutshell, don't do anything if there's no note or there's no
- # NOSY
- if self.form.has_key(':note'):
- # fix the CRLF/CR -> LF stuff
- note = fixNewlines(self.form[':note'].value.strip())
- if not note:
- return None, files
- if not props.has_key('messages'):
- return None, files
- if not isinstance(props['messages'], hyperdb.Multilink):
- return None, files
- if not props['messages'].classname == 'msg':
- return None, files
- if not (self.form.has_key('nosy') or note):
- return None, files
-
- # handle the note
- if '\n' in note:
- summary = re.split(r'\n\r?', note)[0]
- else:
- summary = note
- m = ['%s\n'%note]
-
- # handle the messageid
- # TODO: handle inreplyto
- messageid = "<%s.%s.%s@%s>"%(time.time(), random.random(),
- self.classname, self.instance.config.MAIL_DOMAIN)
-
- # see if there are any message properties we should set
- msg_props={};
- if self.form.has_key(':msg_fields'):
- for field in self.form[':msg_fields'].value.split(','):
- if self.form.has_key(field):
- if field.startswith("msg_"):
- msg_props[field[4:]] = self.form[field].value
- else :
- msg_props[field] = self.form[field].value
-
- # now create the message, attaching the files
- content = '\n'.join(m)
- message_id = self.db.msg.create(author=self.userid,
- recipients=[], date=date.Date('.'), summary=summary,
- content=content, files=files, messageid=messageid, **msg_props)
-
- # update the messages property
- return message_id, files
-
- def _post_editnode(self, nid):
- '''Do the linking part of the node creation.
-
- If a form element has :link or :multilink appended to it, its
- value specifies a node designator and the property on that node
- to add _this_ node to as a link or multilink.
-
- This is typically used on, eg. the file upload page to indicated
- which issue to link the file to.
+ # check for permission
+ if not self.editItemPermission(props):
+ raise PermissionError, 'You do not have permission to edit %s'%cn
+
+ # make the changes
+ cl = self.db.classes[cn]
+ return cl.set(nodeid, **props)
+
+ def _createnode(self, cn, props):
+ ''' create a node based on the contents of the form
'''
- cn = self.classname
+ # check for permission
+ if not self.newItemPermission(props):
+ raise PermissionError, 'You do not have permission to create %s'%cn
+
+ # create the node and return its id
cl = self.db.classes[cn]
- # link if necessary
- keys = self.form.keys()
- for key in keys:
- if key == ':multilink':
- value = self.form[key].value
- if type(value) != type([]): value = [value]
- for value in value:
- designator, property = value.split(':')
- link, nodeid = hyperdb.splitDesignator(designator)
- link = self.db.classes[link]
- # take a dupe of the list so we're not changing the cache
- value = link.get(nodeid, property)[:]
- value.append(nid)
- link.set(nodeid, **{property: value})
- elif key == ':link':
- value = self.form[key].value
- if type(value) != type([]): value = [value]
- for value in value:
- designator, property = value.split(':')
- link, nodeid = hyperdb.splitDesignator(designator)
- link = self.db.classes[link]
- link.set(nodeid, **{property: nid})
+ return cl.create(**props)
def parsePropsFromForm(self, num_re=re.compile('^\d+$')):
''' Pull properties for the given class out of the form.
- If a ":required" parameter is supplied, then the names
- property values must be supplied or a ValueError will be raised.
+ In the following, <bracketed> values are variable, ":" may be
+ any of : @ + and other text "required" is fixed.
+
+ Properties are specified as form variables
+ <designator>:<propname>
+
+ where the propery belongs to the context class or item if the
+ designator is not specified. The designator may specify a
+ negative item id value (ie. "issue-1") and a new item of the
+ specified class will be created for each negative id found.
+
+ If a "<designator>:required" parameter is supplied,
+ then the named property values must be supplied or a
+ ValueError will be raised.
Other special form values:
- :remove:<propname>=id(s)
+ [classname|designator]:remove:<propname>=id(s)
The ids will be removed from the multilink property.
- :add:<propname>=id(s)
+ [classname|designator]:add:<propname>=id(s)
The ids will be added to the multilink property.
+ [classname|designator]:link:<propname>=<classname>
+ Used to add a link to new items created during edit.
+ These are collected up and returned in all_links. This will
+ result in an additional linking operation (either Link set or
+ Multilink append) after the edit/create is done using
+ all_props in _editnodes. The <propname> on
+ [classname|designator] will be set/appended the id of the
+ newly created item of class <classname>.
+
Note: the colon may be one of: : @ +
Any of the form variables may be prefixed with a classname or
designator.
The return from this method is a dict of
- classname|designator: properties
+ (classname, id): properties
... this dict _always_ has an entry for the current context,
even if it's empty (ie. a submission for an existing issue that
- doesn't result in any changes would return {'issue123': {}})
+ doesn't result in any changes would return {('issue','123'): {}})
+ The id may be None, which indicates that an item should be
+ created.
+
+ If a String property's form value is a file upload, then we
+ try to set additional properties "filename" and "type" (if
+ they are valid for the class).
'''
# some very useful variables
db = self.db
form = self.form
- if not hasattr(self, 'FV_CLASSSPEC'):
+ if not hasattr(self, 'FV_ITEMSPEC'):
# generate the regexp for detecting
# <classname|designator>[@:+]property
classes = '|'.join(db.classes.keys())
- self.FV_CLASSSPEC = re.compile(r'(%s)[@+:](.+)$'%classes)
- self.FV_ITEMSPEC = re.compile(r'(%s)(\d+)[@+:](.+)$'%classes)
+ self.FV_ITEMSPEC = re.compile(r'(%s)([-\d]+)[@+:](.+)$'%classes)
+ self.FV_DESIGNATOR = re.compile(r'(%s)([-\d]+)'%classes)
# these indicate the default class / item
default_cn = self.classname
default_cl = self.db.classes[default_cn]
- default_nodeid = str(self.nodeid or '')
+ default_nodeid = self.nodeid
# we'll store info about the individual class/item edit in these
all_required = {} # one entry per class/item
all_props = {} # one entry per class/item
all_propdef = {} # note - only one entry per class
+ all_links = [] # as many as are required
# we should always return something, even empty, for the context
- all_props[default_cn+default_nodeid] = {}
+ all_props[(default_cn, default_nodeid)] = {}
keys = form.keys()
timezone = db.getUserTimezone()
for key in keys:
- # see if this value modifies a different class/item to the default
- m = self.FV_CLASSSPEC.match(key)
+ # see if this value modifies a different item to the default
+ m = self.FV_ITEMSPEC.match(key)
if m:
- # we got a classname
+ # we got a designator
cn = m.group(1)
cl = self.db.classes[cn]
- nodeid = ''
- propname = m.group(2)
+ nodeid = m.group(2)
+ propname = m.group(3)
+ elif key == ':note':
+ # backwards compatibility: the special note field
+ cn = 'msg'
+ cl = self.db.classes[cn]
+ nodeid = '-1'
+ propname = 'content'
+ all_links.append((default_cn, default_nodeid, 'messages',
+ [('msg', '-1')]))
+ elif key == ':file':
+ # backwards compatibility: the special file field
+ cn = 'file'
+ cl = self.db.classes[cn]
+ nodeid = '-1'
+ propname = 'content'
+ all_links.append((default_cn, default_nodeid, 'files',
+ [('file', '-1')]))
+ if self.form.has_key(':note'):
+ all_links.append(('msg', '-1', 'files', [('file', '-1')]))
else:
- m = self.FV_ITEMSPEC.match(key)
- if m:
- # we got a designator
- cn = m.group(1)
- cl = self.db.classes[cn]
- nodeid = m.group(2)
- propname = m.group(3)
- else:
- # default
- cn = default_cn
- cl = default_cl
- nodeid = default_nodeid
- propname = key
+ # default
+ cn = default_cn
+ cl = default_cl
+ nodeid = default_nodeid
+ propname = key
# the thing this value relates to is...
- this = cn+nodeid
+ this = (cn, nodeid)
+
+ # is this a link command?
+ if self.FV_LINK.match(propname):
+ value = []
+ for entry in extractFormList(form[key]):
+ m = self.FV_DESIGNATOR.match(entry)
+ if not m:
+ raise ValueError, \
+ 'link "%s" value "%s" not a designator'%(key, entry)
+ value.append((m.groups(1), m.groups(2)))
+ all_links.append((cn, nodeid, propname[6:], value))
# get more info about the class, and the current set of
# form props for it
# detect the special ":required" variable
if self.FV_REQUIRED.match(key):
- value = form[key]
- if isinstance(value, type([])):
- required = [i.value.strip() for i in value]
- else:
- required = [i.strip() for i in value.value.split(',')]
- all_required[this] = required
+ all_required[this] = extractFormList(form[key])
continue
# get the required values list
# handle unpacking of the MiniFieldStorage / list form value
if isinstance(proptype, hyperdb.Multilink):
- # multiple values are OK
- if isinstance(value, type([])):
- # it's a list of MiniFieldStorages
- value = [i.value.strip() for i in value]
- else:
- # it's a MiniFieldStorage, but may be a comma-separated list
- # of values
- value = [i.strip() for i in value.value.split(',')]
-
- # filter out the empty bits
- value = filter(None, value)
+ value = extractFormList(value)
else:
# multiple values are not OK
if isinstance(value, type([])):
raise ValueError, 'You have submitted more than one value'\
' for the %s property'%propname
- # we've got a MiniFieldStorage, so pull out the value and strip
- # surrounding whitespace
- value = value.value.strip()
+ # value might be a file upload...
+ if not hasattr(value, 'filename') or value.filename is None:
+ # nope, pull out the value and strip it
+ value = value.value.strip()
# handle by type now
if isinstance(proptype, hyperdb.Password):
# see if it's the "no selection" choice
if value == '-1' or not value:
# if we're creating, just don't include this property
- if not nodeid:
+ if not nodeid or nodeid.startswith('-'):
continue
value = None
else:
# we're modifying the list - get the current list of ids
if props.has_key(propname):
existing = props[propname]
- elif nodeid:
+ elif nodeid and not nodeid.startswith('-'):
existing = cl.get(nodeid, propname, [])
else:
existing = []
value = existing
value.sort()
- # other types should be None'd if there's no value
- elif value:
+ elif value == '':
+ # if we're creating, just don't include this property
+ if not nodeid or nodeid.startswith('-'):
+ continue
+ # other types should be None'd if there's no value
+ value = None
+ else:
if isinstance(proptype, hyperdb.String):
- # fix the CRLF/CR -> LF stuff
- value = fixNewlines(value)
+ if (hasattr(value, 'filename') and
+ value.filename is not None):
+ # this String is actually a _file_
+ # try to determine the file content-type
+ filename = value.filename.split('\\')[-1]
+ if propdef.has_key('name'):
+ props['name'] = filename
+ # use this info as the type/filename properties
+ if propdef.has_key('type'):
+ props['type'] = mimetypes.guess_type(filename)[0]
+ if not props['type']:
+ props['type'] = "application/octet-stream"
+ # finally, read the content
+ value = value.value
+ else:
+ # normal String fix the CRLF/CR -> LF stuff
+ value = fixNewlines(value)
+
elif isinstance(proptype, hyperdb.Date):
value = date.Date(value, offset=timezone)
elif isinstance(proptype, hyperdb.Interval):
value = value.lower() in ('yes', 'true', 'on', '1')
elif isinstance(proptype, hyperdb.Number):
value = float(value)
- else:
- # if we're creating, just don't include this property
- if not nodeid:
- continue
- value = None
# get the old value
- if nodeid:
+ if nodeid and not nodeid.startswith('-'):
try:
existing = cl.get(nodeid, propname)
except KeyError:
p = 'properties'
else:
p = 'property'
- s.append('Required %s %s %s not supplied'%(thing, p,
+ s.append('Required %s %s %s not supplied'%(thing[0], p,
', '.join(required)))
if s:
raise ValueError, '\n'.join(s)
- return all_props
+ return all_props, all_links
def fixNewlines(text):
''' Homogenise line endings.
'''
text = text.replace('\r\n', '\n')
return text.replace('\r', '\n')
+
+def extractFormList(value):
+ ''' Extract a list of values from the form value.
+
+ It may be one of:
+ [MiniFieldStorage, MiniFieldStorage, ...]
+ MiniFieldStorage('value,value,...')
+ MiniFieldStorage('value')
+ '''
+ # multiple values are OK
+ if isinstance(value, type([])):
+ # it's a list of MiniFieldStorages
+ value = [i.value.strip() for i in value]
+ else:
+ # it's a MiniFieldStorage, but may be a comma-separated list
+ # of values
+ value = [i.strip() for i in value.value.split(',')]
+
+ # filter out the empty bits
+ return filter(None, value)
+
diff --git a/test/test_cgi.py b/test/test_cgi.py
index 6625b7f090a9ecb495117de5a89eff8766a3a24a..b0a2cd0c1fb6e993d49c338b8dd30e0b75fc320d 100644 (file)
--- a/test/test_cgi.py
+++ b/test/test_cgi.py
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
-# $Id: test_cgi.py,v 1.7 2003-02-12 06:41:58 richard Exp $
+# $Id: test_cgi.py,v 1.8 2003-02-13 07:38:34 richard Exp $
import unittest, os, shutil, errno, sys, difflib, cgi
from roundup.cgi import client
from roundup import init, instance, password, hyperdb, date
+class FileUpload:
+ def __init__(self, content, filename):
+ self.content = content
+ self.filename = filename
+
def makeForm(args):
form = cgi.FieldStorage()
for k,v in args.items():
if type(v) is type([]):
[form.list.append(cgi.MiniFieldStorage(k, x)) for x in v]
+ elif isinstance(v, FileUpload):
+ x = cgi.MiniFieldStorage(k, v.content)
+ x.filename = v.filename
+ form.list.append(x)
else:
form.list.append(cgi.MiniFieldStorage(k, v))
return form
# Empty form
#
def testNothing(self):
- self.assertEqual(self.parseForm({}), {'test': {}})
+ self.assertEqual(self.parseForm({}), ({('test', None): {}}, []))
def testNothingWithRequired(self):
self.assertRaises(ValueError, self.parseForm, {':required': 'string'})
# String
#
def testEmptyString(self):
- self.assertEqual(self.parseForm({'string': ''}), {'test': {}})
- self.assertEqual(self.parseForm({'string': ' '}), {'test': {}})
+ self.assertEqual(self.parseForm({'string': ''}),
+ ({('test', None): {}}, []))
+ self.assertEqual(self.parseForm({'string': ' '}),
+ ({('test', None): {}}, []))
self.assertRaises(ValueError, self.parseForm, {'string': ['', '']})
def testSetString(self):
self.assertEqual(self.parseForm({'string': 'foo'}),
- {'test': {'string': 'foo'}})
+ ({('test', None): {'string': 'foo'}}, []))
self.assertEqual(self.parseForm({'string': 'a\r\nb\r\n'}),
- {'test': {'string': 'a\nb'}})
+ ({('test', None): {'string': 'a\nb'}}, []))
nodeid = self.db.issue.create(title='foo')
self.assertEqual(self.parseForm({'title': 'foo'}, 'issue', nodeid),
- {'issue'+nodeid: {}})
+ ({('issue', nodeid): {}}, []))
def testEmptyStringSet(self):
nodeid = self.db.issue.create(title='foo')
self.assertEqual(self.parseForm({'title': ''}, 'issue', nodeid),
- {'issue'+nodeid: {'title': None}})
+ ({('issue', nodeid): {'title': None}}, []))
nodeid = self.db.issue.create(title='foo')
self.assertEqual(self.parseForm({'title': ' '}, 'issue', nodeid),
- {'issue'+nodeid: {'title': None}})
+ ({('issue', nodeid): {'title': None}}, []))
+
+ def testFileUpload(self):
+ file = FileUpload('foo', 'foo.txt')
+ self.assertEqual(self.parseForm({'content': file}, 'file'),
+ ({('file', None): {'content': 'foo', 'name': 'foo.txt',
+ 'type': 'text/plain'}}, []))
#
# Link
#
def testEmptyLink(self):
- self.assertEqual(self.parseForm({'link': ''}), {'test': {}})
- self.assertEqual(self.parseForm({'link': ' '}), {'test': {}})
+ self.assertEqual(self.parseForm({'link': ''}),
+ ({('test', None): {}}, []))
+ self.assertEqual(self.parseForm({'link': ' '}),
+ ({('test', None): {}}, []))
self.assertRaises(ValueError, self.parseForm, {'link': ['', '']})
- self.assertEqual(self.parseForm({'link': '-1'}), {'test': {}})
+ self.assertEqual(self.parseForm({'link': '-1'}),
+ ({('test', None): {}}, []))
def testSetLink(self):
self.assertEqual(self.parseForm({'status': 'unread'}, 'issue'),
- {'issue': {'status': '1'}})
+ ({('issue', None): {'status': '1'}}, []))
self.assertEqual(self.parseForm({'status': '1'}, 'issue'),
- {'issue': {'status': '1'}})
+ ({('issue', None): {'status': '1'}}, []))
nodeid = self.db.issue.create(status='unread')
self.assertEqual(self.parseForm({'status': 'unread'}, 'issue', nodeid),
- {'issue'+nodeid: {}})
+ ({('issue', nodeid): {}}, []))
def testUnsetLink(self):
nodeid = self.db.issue.create(status='unread')
self.assertEqual(self.parseForm({'status': '-1'}, 'issue', nodeid),
- {'issue'+nodeid: {'status': None}})
+ ({('issue', nodeid): {'status': None}}, []))
def testInvalidLinkValue(self):
# XXX This is not the current behaviour - should we enforce this?
# self.assertRaises(IndexError, self.parseForm,
# {'status': '4'}))
self.assertRaises(ValueError, self.parseForm, {'link': 'frozzle'})
-
- self.assertRaises(ValueError, self.parseForm, {'link': 'frozzle'})
+ self.assertRaises(ValueError, self.parseForm, {'status': 'frozzle'},
+ 'issue')
#
# Multilink
#
def testEmptyMultilink(self):
- self.assertEqual(self.parseForm({'nosy': ''}), {'test': {}})
- self.assertEqual(self.parseForm({'nosy': ' '}), {'test': {}})
+ self.assertEqual(self.parseForm({'nosy': ''}),
+ ({('test', None): {}}, []))
+ self.assertEqual(self.parseForm({'nosy': ' '}),
+ ({('test', None): {}}, []))
def testSetMultilink(self):
self.assertEqual(self.parseForm({'nosy': '1'}, 'issue'),
- {'issue': {'nosy': ['1']}})
+ ({('issue', None): {'nosy': ['1']}}, []))
self.assertEqual(self.parseForm({'nosy': 'admin'}, 'issue'),
- {'issue': {'nosy': ['1']}})
+ ({('issue', None): {'nosy': ['1']}}, []))
self.assertEqual(self.parseForm({'nosy': ['1','2']}, 'issue'),
- {'issue': {'nosy': ['1','2']}})
+ ({('issue', None): {'nosy': ['1','2']}}, []))
self.assertEqual(self.parseForm({'nosy': '1,2'}, 'issue'),
- {'issue': {'nosy': ['1','2']}})
+ ({('issue', None): {'nosy': ['1','2']}}, []))
self.assertEqual(self.parseForm({'nosy': 'admin,2'}, 'issue'),
- {'issue': {'nosy': ['1','2']}})
+ ({('issue', None): {'nosy': ['1','2']}}, []))
def testEmptyMultilinkSet(self):
nodeid = self.db.issue.create(nosy=['1','2'])
self.assertEqual(self.parseForm({'nosy': ''}, 'issue', nodeid),
- {'issue'+nodeid: {'nosy': []}})
+ ({('issue', nodeid): {'nosy': []}}, []))
nodeid = self.db.issue.create(nosy=['1','2'])
self.assertEqual(self.parseForm({'nosy': ' '}, 'issue', nodeid),
- {'issue'+nodeid: {'nosy': []}})
+ ({('issue', nodeid): {'nosy': []}}, []))
self.assertEqual(self.parseForm({'nosy': '1,2'}, 'issue', nodeid),
- {'issue'+nodeid: {}})
+ ({('issue', nodeid): {}}, []))
def testInvalidMultilinkValue(self):
# XXX This is not the current behaviour - should we enforce this?
nodeid = self.db.issue.create(nosy=['1'])
# do nothing
self.assertEqual(self.parseForm({':add:nosy': ''}, 'issue', nodeid),
- {'issue'+nodeid: {}})
+ ({('issue', nodeid): {}}, []))
# do something ;)
self.assertEqual(self.parseForm({':add:nosy': '2'}, 'issue', nodeid),
- {'issue'+nodeid: {'nosy': ['1','2']}})
+ ({('issue', nodeid): {'nosy': ['1','2']}}, []))
self.assertEqual(self.parseForm({':add:nosy': '2,mary'}, 'issue',
- nodeid), {'issue'+nodeid: {'nosy': ['1','2','4']}})
+ nodeid), ({('issue', nodeid): {'nosy': ['1','2','4']}}, []))
self.assertEqual(self.parseForm({':add:nosy': ['2','3']}, 'issue',
- nodeid), {'issue'+nodeid: {'nosy': ['1','2','3']}})
+ nodeid), ({('issue', nodeid): {'nosy': ['1','2','3']}}, []))
def testMultilinkAddNew(self):
self.assertEqual(self.parseForm({':add:nosy': ['2','3']}, 'issue'),
- {'issue': {'nosy': ['2','3']}})
+ ({('issue', None): {'nosy': ['2','3']}}, []))
def testMultilinkRemove(self):
nodeid = self.db.issue.create(nosy=['1','2'])
# do nothing
self.assertEqual(self.parseForm({':remove:nosy': ''}, 'issue', nodeid),
- {'issue'+nodeid: {}})
+ ({('issue', nodeid): {}}, []))
# do something ;)
self.assertEqual(self.parseForm({':remove:nosy': '1'}, 'issue',
- nodeid), {'issue'+nodeid: {'nosy': ['2']}})
+ nodeid), ({('issue', nodeid): {'nosy': ['2']}}, []))
self.assertEqual(self.parseForm({':remove:nosy': 'admin,2'},
- 'issue', nodeid), {'issue'+nodeid: {'nosy': []}})
+ 'issue', nodeid), ({('issue', nodeid): {'nosy': []}}, []))
self.assertEqual(self.parseForm({':remove:nosy': ['1','2']},
- 'issue', nodeid), {'issue'+nodeid: {'nosy': []}})
+ 'issue', nodeid), ({('issue', nodeid): {'nosy': []}}, []))
+
+ # add and remove
+ self.assertEqual(self.parseForm({':add:nosy': ['3'],
+ ':remove:nosy': ['1','2']},
+ 'issue', nodeid), ({('issue', nodeid): {'nosy': ['3']}}, []))
# remove one that doesn't exist?
self.assertRaises(ValueError, self.parseForm, {':remove:nosy': '4'},
def testMultilinkRetired(self):
self.db.user.retire('2')
self.assertEqual(self.parseForm({'nosy': ['2','3']}, 'issue'),
- {'issue': {'nosy': ['2','3']}})
+ ({('issue', None): {'nosy': ['2','3']}}, []))
nodeid = self.db.issue.create(nosy=['1','2'])
self.assertEqual(self.parseForm({':remove:nosy': '2'}, 'issue',
- nodeid), {'issue'+nodeid: {'nosy': ['1']}})
+ nodeid), ({('issue', nodeid): {'nosy': ['1']}}, []))
self.assertEqual(self.parseForm({':add:nosy': '3'}, 'issue', nodeid),
- {'issue'+nodeid: {'nosy': ['1','2','3']}})
+ ({('issue', nodeid): {'nosy': ['1','2','3']}}, []))
def testAddRemoveNonexistant(self):
self.assertRaises(ValueError, self.parseForm, {':remove:foo': '2'},
#
def testEmptyPassword(self):
self.assertEqual(self.parseForm({'password': ''}, 'user'),
- {'user': {}})
+ ({('user', None): {}}, []))
self.assertEqual(self.parseForm({'password': ''}, 'user'),
- {'user': {}})
+ ({('user', None): {}}, []))
self.assertRaises(ValueError, self.parseForm, {'password': ['', '']},
'user')
self.assertRaises(ValueError, self.parseForm, {'password': 'foo',
def testSetPassword(self):
self.assertEqual(self.parseForm({'password': 'foo',
- 'password:confirm': 'foo'}, 'user'), {'user': {'password': 'foo'}})
+ 'password:confirm': 'foo'}, 'user'),
+ ({('user', None): {'password': 'foo'}}, []))
def testSetPasswordConfirmBad(self):
self.assertRaises(ValueError, self.parseForm, {'password': 'foo'},
nodeid = self.db.user.create(username='1',
password=password.Password('foo'))
self.assertEqual(self.parseForm({'password': ''}, 'user', nodeid),
- {'user'+nodeid: {}})
+ ({('user', nodeid): {}}, []))
nodeid = self.db.user.create(username='2',
password=password.Password('foo'))
self.assertEqual(self.parseForm({'password': '',
'password:confirm': ''}, 'user', nodeid),
- {'user'+nodeid: {}})
+ ({('user', nodeid): {}}, []))
#
# Boolean
#
def testEmptyBoolean(self):
- self.assertEqual(self.parseForm({'boolean': ''}), {'test': {}})
- self.assertEqual(self.parseForm({'boolean': ' '}), {'test': {}})
+ self.assertEqual(self.parseForm({'boolean': ''}),
+ ({('test', None): {}}, []))
+ self.assertEqual(self.parseForm({'boolean': ' '}),
+ ({('test', None): {}}, []))
self.assertRaises(ValueError, self.parseForm, {'boolean': ['', '']})
def testSetBoolean(self):
self.assertEqual(self.parseForm({'boolean': 'yes'}),
- {'test': {'boolean': 1}})
+ ({('test', None): {'boolean': 1}}, []))
self.assertEqual(self.parseForm({'boolean': 'a\r\nb\r\n'}),
- {'test': {'boolean': 0}})
+ ({('test', None): {'boolean': 0}}, []))
nodeid = self.db.test.create(boolean=1)
self.assertEqual(self.parseForm({'boolean': 'yes'}, 'test', nodeid),
- {'test'+nodeid: {}})
+ ({('test', nodeid): {}}, []))
nodeid = self.db.test.create(boolean=0)
self.assertEqual(self.parseForm({'boolean': 'no'}, 'test', nodeid),
- {'test'+nodeid: {}})
+ ({('test', nodeid): {}}, []))
def testEmptyBooleanSet(self):
nodeid = self.db.test.create(boolean=0)
self.assertEqual(self.parseForm({'boolean': ''}, 'test', nodeid),
- {'test'+nodeid: {'boolean': None}})
+ ({('test', nodeid): {'boolean': None}}, []))
nodeid = self.db.test.create(boolean=1)
self.assertEqual(self.parseForm({'boolean': ' '}, 'test', nodeid),
- {'test'+nodeid: {'boolean': None}})
+ ({('test', nodeid): {'boolean': None}}, []))
#
# Date
#
def testEmptyDate(self):
- self.assertEqual(self.parseForm({'date': ''}), {'test': {}})
- self.assertEqual(self.parseForm({'date': ' '}), {'test': {}})
+ self.assertEqual(self.parseForm({'date': ''}),
+ ({('test', None): {}}, []))
+ self.assertEqual(self.parseForm({'date': ' '}),
+ ({('test', None): {}}, []))
self.assertRaises(ValueError, self.parseForm, {'date': ['', '']})
def testSetDate(self):
self.assertEqual(self.parseForm({'date': '2003-01-01'}),
- {'test': {'date': date.Date('2003-01-01')}})
+ ({('test', None): {'date': date.Date('2003-01-01')}}, []))
nodeid = self.db.test.create(date=date.Date('2003-01-01'))
self.assertEqual(self.parseForm({'date': '2003-01-01'}, 'test',
- nodeid), {'test'+nodeid: {}})
+ nodeid), ({('test', nodeid): {}}, []))
def testEmptyDateSet(self):
nodeid = self.db.test.create(date=date.Date('.'))
self.assertEqual(self.parseForm({'date': ''}, 'test', nodeid),
- {'test'+nodeid: {'date': None}})
+ ({('test', nodeid): {'date': None}}, []))
nodeid = self.db.test.create(date=date.Date('1970-01-01.00:00:00'))
self.assertEqual(self.parseForm({'date': ' '}, 'test', nodeid),
- {'test'+nodeid: {'date': None}})
+ ({('test', nodeid): {'date': None}}, []))
#
# Test multiple items in form
#
def testMultiple(self):
- self.assertEqual(self.parseForm({'string': 'a', 'issue@title': 'b'}),
- {'test': {'string': 'a'}, 'issue': {'title': 'b'}})
+ self.assertEqual(self.parseForm({'string': 'a', 'issue-1@title': 'b'}),
+ ({('test', None): {'string': 'a'}, ('issue', '-1'):
+ {'title': 'b'}}, []))
nodeid = self.db.test.create()
- self.assertEqual(self.parseForm({'string': 'a', 'issue@title': 'b'},
- 'test', nodeid),
- {'test1': {'string': 'a'}, 'issue': {'title': 'b'}})
+ self.assertEqual(self.parseForm({'string': 'a', 'issue-1@title': 'b'},
+ 'test', nodeid), ({('test', nodeid): {'string': 'a'},
+ ('issue', '-1'): {'title': 'b'}}, []))
+ self.assertEqual(self.parseForm({
+ 'string': 'a',
+ 'issue-1@:add:nosy': '1',
+ 'issue-2@:link:superseder': 'issue-1',
+ }),
+ ({('test', None): {'string': 'a'},
+ ('issue', '-1'): {'nosy': ['1']},
+ ('issue', '-2'): {}},
+ [('issue', '-2', 'superseder',
+ [(('issue', '-1'), ('issue', '-1'))])
+ ]
+ )
+ )
+
+ def testLinkBadDesignator(self):
+ self.assertRaises(ValueError, self.parseForm,
+ {'test-1@:link:link': 'blah'})
+ self.assertRaises(ValueError, self.parseForm,
+ {'test-1@:link:link': 'issue'})
+
+ def testBackwardsCompat(self):
+ self.assertEqual(self.parseForm({':note': 'spam'}, 'issue'),
+ ({('issue', None): {}, ('msg', '-1'): {'content': 'spam'}},
+ [('issue', None, 'messages', [('msg', '-1')])]))
+ file = FileUpload('foo', 'foo.txt')
+ self.assertEqual(self.parseForm({':file': file}, 'issue'),
+ ({('issue', None): {}, ('file', '-1'): {'content': 'foo',
+ 'name': 'foo.txt', 'type': 'text/plain'}},
+ [('issue', None, 'files', [('file', '-1')])]))
def suite():
l = [unittest.makeSuite(FormTestCase),
]
return unittest.TestSuite(l)
+def run():
+ runner = unittest.TextTestRunner()
+ unittest.main(testRunner=runner)
+
+if __name__ == '__main__':
+ run()
# vim: set filetype=python ts=4 sw=4 et si