[qmtest] Bug with editing attachments
Mark Mitchell
mark at codesourcery.com
Thu Jan 15 06:11:25 UTC 2004
On Thu, 2003-11-20 at 04:09, Vladimir Prus wrote:
> Hello,
> using CVS HEAD of QMTest, I have problems with attachments.
Volodya --
I've (finally!) gotten around to fixing this problem, I think.
Here's a patch which cleans up the Attachment abstractions
substantially. I've checked in the patch, but I'd appreciate it if you
would try it out and let me know if it works for you.
Thanks!
--
Mark Mitchell <mark at codesourcery.com>
CodeSourcery, LLC
-------------- next part --------------
2004-01-14 Mark Mitchell <mark at codesourcery.com>
* qm/attachment.py (Attachment.Move): New method.
(AttachmentStore.Store): Do not return a value.
(TemporaryAttachmentStore.Store): Do not check
is_temporary_location.
(is_temporary_location): Remove.
(make_dom_node): Do not check is_temporary_location.
* qm/extension.py (Extension.MakeDomElement): Remove debugging
code.
* qm/fields.py (Field.GetSubfields): New method.
(Field.ParseFormValue): Take a dictionary of attachment stores as
a parameter, not a single attachment store.
(TextField.ParseFormValue): Likewise.
(TupleField.GetSubfields): New method.
(TupleField.ParseFormValue): Take a dictionary of attachment
stores as a parameter, not a single attachment store.
(SetField.GetContainedField): Remove.
(SetField.GetHelp): Adjust.
(SetField.GetSubfields): New method.
(SetField.MakeDomNodeForValue): Do not use GetContainedField.
(SetField.ParseFormValue): Likewise. Take a dictionary of attachment
stores as a parameter, not a single attachment store.
(UploadAttachmentPage.__next_temporary_location): Remove.
(UploadAttachmentPage.__init__): Add attachment_store parameter.
(AttachmentField.FormatValueAsHTML): Encode the attachment store.
(AttachmentField.ParseFormValue): Decode it.
* qm/web.py (WebServer.__init__): Create a temporary attachment
store.
(WebServer.GetTemporaryAttachmentStore): New method.
* qm/test/runnable.py (Runnable.GetAttachments): New method.
(Runnable.__GetAttachments): Likewise.
* qm/test/classes/xml_database.py
(XMLDatabase.__StoreAttachments): Rewrite.
* qm/test/web/web.py (QMTestServer.__init__): Do not create a
temporary attachment store.
(QMTestServer.HandleSubmitItem): Pass all available attachment
stores to ParseFormValue. Do not try to remove unused attachments
here.
* share/dtml/attachment.dtml: Encode the attachment store id.
Index: qm/attachment.py
===================================================================
RCS file: /home/qm/Repository/qm/qm/attachment.py,v
retrieving revision 1.20
diff -c -5 -p -r1.20 attachment.py
*** qm/attachment.py 21 Jul 2003 19:38:28 -0000 1.20
--- qm/attachment.py 15 Jan 2004 05:59:28 -0000
*************** class Attachment:
*** 154,163 ****
--- 154,183 ----
returns -- The 'AttachmentStore' that contains this attachment."""
return self.__store
+
+ def Move(self, store, location):
+ """Move the 'Attachment' to a new location.
+
+ 'store' -- The 'AttachmentStore' that will contain the
+ attachment.
+
+ 'location' -- The location of the attachment within its current
+ store."""
+
+ # Store this attachment in its new location. That must be done
+ # before removing it from its current location as that step will
+ # destroy the data contained in the attachment.
+ store.Store(self, location)
+ # Now, remove the attachment from its current location.
+ self.__store.Remove(self.__location)
+ # Finally, update the information associated with the attachment.
+ self.__store = store
+ self.__location = location
+
def __str__(self):
return '<Attachment "%s" (%s)>' \
% (self.GetDescription(), self.GetMimeType())
*************** class AttachmentStore(object):
*** 236,249 ****
def Store(self, attachment, location):
"""Add an attachment to the store.
'attachment' -- The 'Attachment' to store.
! 'location' -- The location in which to store the 'attachment'.
!
! returns -- A new 'Attachment' whose 'AttachmentStore' is
! 'self'."""
raise NotImplementedError
--- 256,266 ----
def Store(self, attachment, location):
"""Add an attachment to the store.
'attachment' -- The 'Attachment' to store.
! 'location' -- The location in which to store the 'attachment'."""
raise NotImplementedError
*************** class FileAttachmentStore(AttachmentStor
*** 295,310 ****
# Write the data.
file.write(attachment.GetData())
# Close the file.
file.close()
- return Attachment(attachment.GetMimeType(),
- attachment.GetDescription(),
- attachment.GetFileName(),
- location,
- self)
-
def Remove(self, location):
"""Remove an attachment.
'location' -- The location whose data should be removed."""
--- 312,321 ----
*************** class TemporaryAttachmentStore(FileAttac
*** 348,360 ****
returns -- HTML text of a page that instructs the browser window
to close."""
location = request["location"]
- # Because this data is in the temporary attachment store, the
- # location should be a temporary location.
- assert is_temporary_location(location)
# Create the file.
file = open(self.GetDataFile(location), "w")
# Write the data.
file.write(request["file_data"])
# Close the file.
--- 359,368 ----
*************** _temporary_location_prefix = "_temporary
*** 379,394 ****
def make_temporary_location():
"""Return a unique location for temporary attachment data."""
return _temporary_location_prefix + common.make_unique_tag()
-
- def is_temporary_location(location):
- """Return true if 'location' is a temporary attachment location."""
-
- return location.startswith(_temporary_location_prefix)
-
def make_dom_node(attachment, document):
"""Create a DOM element node for this attachment.
'document' -- A DOM document node in which to create the
--- 387,396 ----
*************** def make_dom_node(attachment, document):
*** 418,432 ****
document, "filename", attachment.GetFileName())
node.appendChild(child)
# Create a location element, to include attachment data by
# reference.
location = attachment.GetLocation()
! # Attchments whose data is in the temporary store should not be
! # externalized.
! assert not is_temporary_location(location)
! child = xmlutil.create_dom_text_element(
! document, "location", location)
node.appendChild(child)
return node
--- 420,430 ----
document, "filename", attachment.GetFileName())
node.appendChild(child)
# Create a location element, to include attachment data by
# reference.
location = attachment.GetLocation()
! child = xmlutil.create_dom_text_element(document, "location", location)
node.appendChild(child)
return node
Index: qm/extension.py
===================================================================
RCS file: /home/qm/Repository/qm/qm/extension.py,v
retrieving revision 1.14
diff -c -5 -p -r1.14 extension.py
*** qm/extension.py 3 Jan 2004 04:02:59 -0000 1.14
--- qm/extension.py 15 Jan 2004 05:59:29 -0000
*************** class Extension(object):
*** 128,147 ****
extension class. The caller is responsible for attaching it to
the 'document'."""
# Get all of the arguments.
arguments = get_class_arguments_as_dictionary(self.__class__)
- import sys
- print >> sys.stderr, arguments
# Determine which subset of the 'arguments' have been set
# explicitly.
explicit_arguments = {}
for name, field in arguments.items():
# Do not record computed fields.
if field.IsComputed():
continue
- print >> sys.stderr, name
if self.__dict__.has_key(name):
explicit_arguments[name] = self.__dict__[name]
return make_dom_element(self.__class__, explicit_arguments,
document, element)
--- 128,144 ----
Index: qm/fields.py
===================================================================
RCS file: /home/qm/Repository/qm/qm/fields.py,v
retrieving revision 1.80
diff -c -5 -p -r1.80 fields.py
*** qm/fields.py 13 Nov 2003 03:08:01 -0000 1.80
--- qm/fields.py 15 Jan 2004 05:59:29 -0000
*************** import xmlutil
*** 66,76 ****
class Field(object):
"""A 'Field' is a named, typed component of a data structure."""
form_field_prefix = "_field_"
-
def __init__(self,
name,
default_value,
title = "",
description = "",
--- 66,75 ----
*************** class Field(object):
*** 188,197 ****
--- 187,205 ----
<hr noshade size="2">
<p>Refer to this field as <tt>%s</tt> in Python expressions.</p>
''' % (self.GetTitle(), description, help, self.GetName(), )
+ def GetSubfields(self):
+ """Returns the sequence of subfields contained in this field.
+
+ returns -- The sequence of subfields contained in this field.
+ If there are no subfields, an empty sequence is returned."""
+
+ return ()
+
+
def IsComputed(self):
"""Returns true if this field is computed automatically.
returns -- True if this field is computed automatically. A
computed field is never displayed to users and is not stored
*************** class Field(object):
*** 315,334 ****
before it is returned."""
raise NotImplemented
! def ParseFormValue(self, request, name, attachment_store):
"""Convert a value submitted from an HTML form.
'request' -- The 'WebRequest' containing a value corresponding
to this field.
'name' -- The name corresponding to this field in the 'request'.
! 'attachment_store' -- The 'AttachmentStore' into which new
! attachments should be placed.
returns -- A pair '(value, redisplay)'. 'value' is the value
for this field, as indicated in 'request'. 'redisplay' is true
if and only if the form should be redisplayed, rather than
committed. If an error occurs, an exception is thrown."""
--- 323,343 ----
before it is returned."""
raise NotImplemented
! def ParseFormValue(self, request, name, attachment_stores):
"""Convert a value submitted from an HTML form.
'request' -- The 'WebRequest' containing a value corresponding
to this field.
'name' -- The name corresponding to this field in the 'request'.
! 'attachment_stores' -- A dictionary mapping 'AttachmentStore' ids
! (in the sense of Python's 'id' built-in) to the
! 'AttachmentStore's themselves.
returns -- A pair '(value, redisplay)'. 'value' is the value
for this field, as indicated in 'request'. 'redisplay' is true
if and only if the form should be redisplayed, rather than
committed. If an error occurs, an exception is thrown."""
*************** class TextField(Field):
*** 655,665 ****
if not self.__multiline:
value = re.sub(" *\n+ *", " ", value)
return value
! def ParseFormValue(self, request, name, attachment_store):
# HTTP specifies text encodings are CR/LF delimited; convert to
# the One True Text Format (TM).
return (self.ParseTextValue(qm.convert_from_dos_text(request[name])),
0)
--- 664,674 ----
if not self.__multiline:
value = re.sub(" *\n+ *", " ", value)
return value
! def ParseFormValue(self, request, name, attachment_stores):
# HTTP specifies text encodings are CR/LF delimited; convert to
# the One True Text Format (TM).
return (self.ParseTextValue(qm.convert_from_dos_text(request[name])),
0)
*************** class TupleField(Field):
*** 721,730 ****
--- 730,745 ----
help += "** " + f.GetTitle() + " **\n\n"
help += f.GetHelp()
return help
+
+ def GetSubfields(self):
+
+ return self.__fields
+
+
### Output methods.
def FormatValueAsHtml(self, server, value, style, name = None):
# Use the default name if none is specified.
*************** class TupleField(Field):
*** 759,775 ****
assert len(value) == len(self.__fields)
return map(lambda f, v: f.Validate(v),
self.__fields, value)
! def ParseFormValue(self, request, name, attachment_store):
value = []
redisplay = 0
for f in self.__fields:
v, r = f.ParseFormValue(request, name + "_" + f.GetName(),
! attachment_store)
value.append(v)
if r:
redisplay = 1
# Now that we've computed the value of the entire tuple, make
--- 774,790 ----
assert len(value) == len(self.__fields)
return map(lambda f, v: f.Validate(v),
self.__fields, value)
! def ParseFormValue(self, request, name, attachment_stores):
value = []
redisplay = 0
for f in self.__fields:
v, r = f.ParseFormValue(request, name + "_" + f.GetName(),
! attachment_stores)
value.append(v)
if r:
redisplay = 1
# Now that we've computed the value of the entire tuple, make
*************** class SetField(Field):
*** 828,851 ****
# Remeber the contained field type.
self.__contained = contained
self.__not_empty_set = not_empty_set == "true"
- def GetContainedField(self):
- """Returns the field instance of the contents of the set."""
-
- return self.__contained
-
-
def GetHelp(self):
return """
A set field. A set contains zero or more elements, all of the
same type. The elements of the set are described below:
! """ + self.GetContainedField().GetHelp()
def GetHtmlHelp(self, edit=0):
help = Field.GetHtmlHelp(self)
if edit:
# In addition to the standard generated help, include
# additional instructions about using the HTML controls.
--- 843,865 ----
# Remeber the contained field type.
self.__contained = contained
self.__not_empty_set = not_empty_set == "true"
def GetHelp(self):
return """
A set field. A set contains zero or more elements, all of the
same type. The elements of the set are described below:
! """ + self.__contained.GetHelp()
+ def GetSubfields(self):
+
+ return (self.__contained,)
+
+
def GetHtmlHelp(self, edit=0):
help = Field.GetHtmlHelp(self)
if edit:
# In addition to the standard generated help, include
# additional instructions about using the HTML controls.
*************** class SetField(Field):
*** 867,877 ****
# If the set is empty, indicate this specially.
if len(value) == 0:
return "None"
# Format each element of the set, and join them into a
# comma-separated list.
! contained_field = self.GetContainedField()
formatted_items = []
for item in value:
formatted_item = contained_field.FormatValueAsText(item, columns)
formatted_items.append(repr(formatted_item))
result = "[ " + string.join(formatted_items, ", ") + " ]"
--- 881,891 ----
# If the set is empty, indicate this specially.
if len(value) == 0:
return "None"
# Format each element of the set, and join them into a
# comma-separated list.
! contained_field = self.__contained
formatted_items = []
for item in value:
formatted_item = contained_field.FormatValueAsText(item, columns)
formatted_items.append(repr(formatted_item))
result = "[ " + string.join(formatted_items, ", ") + " ]"
*************** class SetField(Field):
*** 884,894 ****
value = []
# Use the default field form field name if requested.
if name is None:
name = self.GetHtmlFormFieldName()
! contained_field = self.GetContainedField()
if style == "brief" or style == "full":
if len(value) == 0:
# An empty set.
return "None"
--- 898,908 ----
value = []
# Use the default field form field name if requested.
if name is None:
name = self.GetHtmlFormFieldName()
! contained_field = self.__contained
if style == "brief" or style == "full":
if len(value) == 0:
# An empty set.
return "None"
*************** class SetField(Field):
*** 958,968 ****
def MakeDomNodeForValue(self, value, document):
# Create a set element.
element = document.createElement("set")
# Add a child node for each item in the set.
! contained_field = self.GetContainedField()
for item in value:
# The contained field knows how to make a DOM node for each
# item in the set.
item_node = contained_field.MakeDomNodeForValue(item, document)
element.appendChild(item_node)
--- 972,982 ----
def MakeDomNodeForValue(self, value, document):
# Create a set element.
element = document.createElement("set")
# Add a child node for each item in the set.
! contained_field = self.__contained
for item in value:
# The contained field knows how to make a DOM node for each
# item in the set.
item_node = contained_field.MakeDomNodeForValue(item, document)
element.appendChild(item_node)
*************** class SetField(Field):
*** 1025,1062 ****
# The next token should be a string constant.
if tok[0] != tokenize.STRING:
invalid(tok)
# Parse the string constant.
v = eval(tok[1])
! elements.append(self.GetContainedField().ParseTextValue(v))
# There should not be any tokens left over.
tok = g.next()
if not tokenize.ISEOF(tok[0]):
invalid(tok)
return self.Validate(elements)
! def ParseFormValue(self, request, name, attachment_store):
values = []
redisplay = 0
# See if the user wants to add or remove elements from the set.
action = request[name]
# Loop over the entries for each of the elements, adding them to
# the set.
! contained_field = self.GetContainedField()
element = 0
for element in xrange(int(request[name + "_count"])):
element_name = name + "_%d" % element
if not (action == "remove"
and request.get(element_name + "_remove") == "on"):
v, r = contained_field.ParseFormValue(request,
element_name,
! attachment_store)
values.append(v)
if r:
redisplay = 1
element += 1
--- 1039,1076 ----
# The next token should be a string constant.
if tok[0] != tokenize.STRING:
invalid(tok)
# Parse the string constant.
v = eval(tok[1])
! elements.append(self.__contained.ParseTextValue(v))
# There should not be any tokens left over.
tok = g.next()
if not tokenize.ISEOF(tok[0]):
invalid(tok)
return self.Validate(elements)
! def ParseFormValue(self, request, name, attachment_stores):
values = []
redisplay = 0
# See if the user wants to add or remove elements from the set.
action = request[name]
# Loop over the entries for each of the elements, adding them to
# the set.
! contained_field = self.__contained
element = 0
for element in xrange(int(request[name + "_count"])):
element_name = name + "_%d" % element
if not (action == "remove"
and request.get(element_name + "_remove") == "on"):
v, r = contained_field.ParseFormValue(request,
element_name,
! attachment_stores)
values.append(v)
if r:
redisplay = 1
element += 1
*************** class SetField(Field):
*** 1097,1107 ****
name=self.GetName(),
right_tag="set",
wrong_tag=node.tagName)
# Use the contained field to extract values for the children of
# this node, which are the set elements.
! contained_field = self.GetContainedField()
fn = lambda n, f=contained_field, s=attachment_store: \
f.GetValueFromDomNode(n, s)
values = map(fn,
filter(lambda n: n.nodeType == xml.dom.Node.ELEMENT_NODE,
node.childNodes))
--- 1111,1121 ----
name=self.GetName(),
right_tag="set",
wrong_tag=node.tagName)
# Use the contained field to extract values for the children of
# this node, which are the set elements.
! contained_field = self.__contained
fn = lambda n, f=contained_field, s=attachment_store: \
f.GetValueFromDomNode(n, s)
values = map(fn,
filter(lambda n: n.nodeType == xml.dom.Node.ELEMENT_NODE,
node.childNodes))
*************** class SetField(Field):
*** 1112,1130 ****
########################################################################
class UploadAttachmentPage(web.DtmlPage):
"""DTML context for generating upload-attachment.dtml."""
! __next_temporary_location = 0
!
! def __init__(self,
field_name,
encoding_name,
summary_field_name,
in_set=0):
"""Create a new page object.
'field_name' -- The user-visible name of the field for which an
attachment is being uploaded.
'encoding_name' -- The name of the HTML input that should
contain the encoded attachment.
--- 1126,1146 ----
########################################################################
class UploadAttachmentPage(web.DtmlPage):
"""DTML context for generating upload-attachment.dtml."""
! def __init__(self,
! attachment_store,
field_name,
encoding_name,
summary_field_name,
in_set=0):
"""Create a new page object.
+ 'attachment_store' -- The AttachmentStore in which the new
+ attachment will be placed.
+
'field_name' -- The user-visible name of the field for which an
attachment is being uploaded.
'encoding_name' -- The name of the HTML input that should
contain the encoded attachment.
*************** class UploadAttachmentPage(web.DtmlPage)
*** 1137,1146 ****
--- 1153,1163 ----
web.DtmlPage.__init__(self, "attachment.dtml")
# Use a brand-new location for the attachment data.
self.location = attachment.make_temporary_location()
# Set up properties.
+ self.attachment_store_id = id(attachment_store)
self.field_name = field_name
self.encoding_name = encoding_name
self.summary_field_name = summary_field_name
self.in_set = in_set
*************** class AttachmentField(Field):
*** 1300,1325 ****
summary_value = 'value="%s"' % self._FormatSummary(value)
if value is None:
field_value = ""
else:
# We'll encode all the relevant information.
! parts = (
! value.GetDescription(),
! value.GetMimeType(),
! value.GetLocation(),
! value.GetFileName(),
! )
# Each part is URL-encoded.
parts = map(urllib.quote, parts)
# The parts are joined into a semicolon-delimited list.
field_value = string.join(parts, ";")
field_value = 'value="%s"' % field_value
# Generate the popup upload page.
! upload_page = UploadAttachmentPage(self.GetTitle(),
! name,
! summary_field_name)()
# Generate controls for this form.
# A text control for the user-visible summary of the
# attachment. The "readonly" property isn't supported in
--- 1317,1340 ----
summary_value = 'value="%s"' % self._FormatSummary(value)
if value is None:
field_value = ""
else:
# We'll encode all the relevant information.
! parts = (description, mime_type, location, file_name,
! str(id(value.GetStore())))
# Each part is URL-encoded.
parts = map(urllib.quote, parts)
# The parts are joined into a semicolon-delimited list.
field_value = string.join(parts, ";")
field_value = 'value="%s"' % field_value
# Generate the popup upload page.
! upload_page = \
! UploadAttachmentPage(server.GetTemporaryAttachmentStore(),
! self.GetTitle(),
! name,
! summary_field_name)()
# Generate controls for this form.
# A text control for the user-visible summary of the
# attachment. The "readonly" property isn't supported in
*************** class AttachmentField(Field):
*** 1394,1404 ****
raise ValueError, \
"the value of an attachment field must be an 'Attachment'"
return value
! def ParseFormValue(self, request, name, attachment_store):
encoding = request[name]
# An empty string represnts a missing attachment, which is OK.
if string.strip(encoding) == "":
return None
--- 1409,1419 ----
raise ValueError, \
"the value of an attachment field must be an 'Attachment'"
return value
! def ParseFormValue(self, request, name, attachment_stores):
encoding = request[name]
# An empty string represnts a missing attachment, which is OK.
if string.strip(encoding) == "":
return None
*************** class AttachmentField(Field):
*** 1406,1420 ****
# relevant information about the attachment.
parts = string.split(encoding, ";")
# Undo the URL encoding of each component.
parts = map(urllib.unquote, parts)
# Unpack the results.
! description, mime_type, location, file_name = parts
# Create the attachment.
value = attachment.Attachment(mime_type, description,
file_name, location,
! attachment_store)
return (self.Validate(value), 0)
def GetValueFromDomNode(self, node, attachment_store):
--- 1421,1438 ----
# relevant information about the attachment.
parts = string.split(encoding, ";")
# Undo the URL encoding of each component.
parts = map(urllib.unquote, parts)
# Unpack the results.
! description, mime_type, location, file_name, store_id = parts
! # Figure out which AttachmentStore corresponds to the id
! # provided.
! store = attachment_stores[int(store_id)]
# Create the attachment.
value = attachment.Attachment(mime_type, description,
file_name, location,
! store)
return (self.Validate(value), 0)
def GetValueFromDomNode(self, node, attachment_store):
Index: qm/web.py
===================================================================
RCS file: /home/qm/Repository/qm/qm/web.py,v
retrieving revision 1.76
diff -c -5 -p -r1.76 web.py
*** qm/web.py 29 Sep 2003 07:03:04 -0000 1.76
--- qm/web.py 15 Jan 2004 05:59:31 -0000
*************** class WebServer(HTTPServer):
*** 745,754 ****
--- 745,760 ----
self.__cache_dir = temporary_directory.TemporaryDirectory()
self.__cache_path = self.__cache_dir.GetPath()
os.mkdir(os.path.join(self.__cache_path, "sessions"), 0700)
+ # Create a temporary attachment store to process attachment data
+ # uploads.
+ self.__temporary_store = qm.attachment.TemporaryAttachmentStore()
+ self.RegisterScript(qm.fields.AttachmentField.upload_url,
+ self.__temporary_store.HandleUploadRequest)
+
# Don't call the base class __init__ here, since we don't want
# to create the web server just yet. Instead, we'll call it
# when it's time to run the server.
*************** class WebServer(HTTPServer):
*** 921,930 ****
--- 927,944 ----
returns -- A pair '(hostname, port)'."""
return (self.server_name, self.server_port)
+ def GetTemporaryAttachmentStore(self):
+ """Return the 'AttachmentStore' used for new 'Attachment's.
+
+ returns -- The 'AttachmentStore' used for new 'Attachment's."""
+
+ return self.__temporary_store
+
+
def MakeButtonForCachedPopup(self,
label,
html_text,
request=None,
window_width=480,
Index: qm/test/runnable.py
===================================================================
RCS file: /home/qm/Repository/qm/qm/test/runnable.py,v
retrieving revision 1.3
diff -c -5 -p -r1.3 runnable.py
*** qm/test/runnable.py 20 Mar 2003 16:55:17 -0000 1.3
--- qm/test/runnable.py 15 Jan 2004 05:59:31 -0000
***************
*** 17,26 ****
--- 17,27 ----
# Imports
########################################################################
import qm
import qm.extension
+ from qm.fields import AttachmentField, TupleField, SetField
########################################################################
# Classes
########################################################################
*************** class Runnable(qm.extension.Extension):
*** 103,107 ****
--- 104,150 ----
returns -- The 'Database' in which this test or resource is
stored."""
return self.__database
+
+
+ def GetAttachments(self):
+ """Return the 'Attachment's to this 'Runnable'.
+
+ returns -- A sequence consisting of the 'Attachment' objects
+ associated with this runnable."""
+
+ attachments = []
+ for f in qm.extension.get_class_arguments(self.__class__):
+ self.__GetAttachments(f,
+ getattr(self, f.GetName()),
+ attachments)
+ return attachments
+
+
+ def __GetAttachments(self, field, value, attachments):
+ """Return the 'Attachments' that are part of 'field'.
+
+ 'field' -- The 'Field' being examined.
+
+ 'value' -- The value of that 'Field' in 'self'.
+
+ 'attachments' -- A sequence consisting of the attachments
+ found so far. Additional 'Attachment's are appended to this
+ sequence by this function."""
+
+ if isinstance(field, AttachmentField):
+ attachments.append(getattr(self, field.GetName()))
+ elif isinstance(field, TupleField):
+ subfields = field.GetSubfields()
+ for i in xrange(len(subfields)):
+ self.__GetAttachments(subfields[i], value[i],
+ attachments)
+ elif isinstance(field, SetField):
+ subfield = field.GetSubfields()[0]
+ for i in xrange(len(value)):
+ self.__GetAttachments(subfield, value[i],
+ attachments)
+
+ return
+
Index: qm/test/classes/xml_database.py
===================================================================
RCS file: /home/qm/Repository/qm/qm/test/classes/xml_database.py,v
retrieving revision 1.17
diff -c -5 -p -r1.17 xml_database.py
*** qm/test/classes/xml_database.py 3 Jan 2004 04:02:59 -0000 1.17
--- qm/test/classes/xml_database.py 15 Jan 2004 05:59:31 -0000
*************** class XMLDatabase(ExtensionDatabase):
*** 85,105 ****
def __StoreAttachments(self, item):
"""Store all attachments in 'item' in the attachment store.
'item' -- A 'Test' or 'Resource'. If any of its fields contain
attachments, add them to the 'AttachmentStore'."""
!
! for field in get_class_arguments(item.__class__):
! if isinstance(field, qm.fields.AttachmentField):
! attachment = getattr(item, field.GetName())
! if (attachment is not None
! and attachment.GetStore() != self.__store):
! location = \
! self.__MakeDataFilePath(item.GetId(),
! attachment.GetFileName())
! setattr(item, field.GetName(),
! self.__store.Store(attachment, location))
def __MakeDataFilePath(self, item_id, file_name):
"""Construct the path to an attachment data file.
--- 85,123 ----
def __StoreAttachments(self, item):
"""Store all attachments in 'item' in the attachment store.
'item' -- A 'Test' or 'Resource'. If any of its fields contain
attachments, add them to the 'AttachmentStore'."""
!
! # Get all of the attachments associated with the new item.
! new_attachments = item.GetAttachments()
!
! # Remove old attachments that are not also among the new
! # attachments.
! store = self.GetAttachmentStore()
! try:
! old_item = self.GetItem(item.kind, item.GetId())
! except:
! old_item = None
! if old_item:
! old_attachments = old_item.GetItem().GetAttachments()
! for o in old_attachments:
! found = 0
! for n in new_attachments:
! if (n.GetStore() == store
! and n.GetFileName() == o.GetFileName()):
! found = 1
! break
! if not found:
! store.Remove(o.GetLocation())
!
! # Put any new attachments into the attachment store.
! for a in new_attachments:
! if a.GetStore() != store:
! location = self.__MakeDataFilePath(item.GetId(),
! a.GetFileName())
! a.Move(store, location)
def __MakeDataFilePath(self, item_id, file_name):
"""Construct the path to an attachment data file.
Index: qm/test/web/web.py
===================================================================
RCS file: /home/qm/Repository/qm/qm/test/web/web.py,v
retrieving revision 1.80
diff -c -5 -p -r1.80 web.py
*** qm/test/web/web.py 12 Jan 2004 22:27:08 -0000 1.80
--- qm/test/web/web.py 15 Jan 2004 05:59:32 -0000
*************** class QMTestServer(qm.web.WebServer):
*** 1573,1587 ****
"/static", qm.get_share_directory("web", "static"))
# Register the QM manual.
self.RegisterPathTranslation(
"/manual", qm.get_doc_directory("test", "html"))
- # Create a temporary attachment store to process attachment data
- # uploads.
- self.__temporary_store = qm.attachment.TemporaryAttachmentStore()
- self.RegisterScript(qm.fields.AttachmentField.upload_url,
- self.__temporary_store.HandleUploadRequest)
# The DB's attachment store processes download requests for
# attachment data.
attachment_store = database.GetAttachmentStore()
if attachment_store:
self.RegisterScript(qm.fields.AttachmentField.download_url,
--- 1573,1582 ----
*************** class QMTestServer(qm.web.WebServer):
*** 2021,2031 ****
field_errors["_id"] = qm.error("test already exists",
test_id=item_id)
# Check that the class exists.
try:
qm.test.base.get_extension_class(class_name, type,
! self.GetDatabase())
except ValueError:
# The class name was incorrectly specified.
field_errors["_class"] = qm.error("invalid class name",
class_name=class_name)
except:
--- 2016,2026 ----
field_errors["_id"] = qm.error("test already exists",
test_id=item_id)
# Check that the class exists.
try:
qm.test.base.get_extension_class(class_name, type,
! database)
except ValueError:
# The class name was incorrectly specified.
field_errors["_class"] = qm.error("invalid class name",
class_name=class_name)
except:
*************** class QMTestServer(qm.web.WebServer):
*** 2261,2289 ****
is_new = int(request["is_new"])
# Extract the class and field specification.
item_class_name = request["class"]
item_class = qm.test.base.get_extension_class(item_class_name,
type,
! self.GetDatabase())
fields = get_class_arguments(item_class)
# We'll perform various kinds of validation as we extract form
# fields. Errors are placed into this map.
field_errors = {}
redisplay = 0
# Loop over fields of the class, looking for arguments in the
# submitted request.
arguments = {}
for field in fields:
# Construct the name we expect for the corresponding argument.
field_name = field.GetName()
form_field_name = field.GetHtmlFormFieldName()
# Parse the value for this field.
try:
value, r = field.ParseFormValue(request, form_field_name,
! self.__temporary_store)
if r:
redisplay = 1
arguments[field_name] = value
except:
# Something went wrong parsing the value. Associate an
--- 2256,2288 ----
is_new = int(request["is_new"])
# Extract the class and field specification.
item_class_name = request["class"]
item_class = qm.test.base.get_extension_class(item_class_name,
type,
! database)
fields = get_class_arguments(item_class)
# We'll perform various kinds of validation as we extract form
# fields. Errors are placed into this map.
field_errors = {}
redisplay = 0
# Loop over fields of the class, looking for arguments in the
# submitted request.
arguments = {}
+ temporary_store = self.GetTemporaryAttachmentStore()
+ main_store = database.GetAttachmentStore()
+ attachment_stores = { id(temporary_store): temporary_store,
+ id(main_store): main_store }
for field in fields:
# Construct the name we expect for the corresponding argument.
field_name = field.GetName()
form_field_name = field.GetHtmlFormFieldName()
# Parse the value for this field.
try:
value, r = field.ParseFormValue(request, form_field_name,
! attachment_stores)
if r:
redisplay = 1
arguments[field_name] = value
except:
# Something went wrong parsing the value. Associate an
*************** class QMTestServer(qm.web.WebServer):
*** 2293,2339 ****
redisplay = 1
if type is "test":
# Create a new test.
item = TestDescriptor(
! self.GetDatabase(),
test_id=item_id,
test_class_name=item_class_name,
arguments=arguments)
elif type is "resource":
# Create a new resource.
! item = ResourceDescriptor(self.GetDatabase(),
! item_id, item_class_name, arguments)
# If necessary, redisplay the form.
if redisplay:
request = qm.web.WebRequest("edit-" + type, base=request,
id=item_id)
return ShowItemPage(self, item, 1, is_new, type,
field_errors)(request)
# Store it in the database.
database.WriteExtension(item_id, item.GetItem())
-
- # Remove any attachments located in the temporary store as they
- # have now been copied to the store associated with the
- # database.
- temporary_store = self.__temporary_store
- for field in fields:
- if isinstance(field, qm.fields.AttachmentField):
- attachment = arguments[field.GetName()]
- if attachment is not None \
- and attachment.GetStore() is temporary_store:
- temporary_store.Remove(attachment.GetLocation())
- elif isinstance(field, qm.fields.SetField) \
- and isinstance(field.GetContainedField(),
- qm.fields.AttachmentField):
- for attachment in arguments[field.GetName()]:
- if attachment is not None \
- and attachment.GetStore() is temporary_store:
- temporary_store.Remove(attachment.GetLocation())
# Redirect to a page that displays the newly-edited item.
request = qm.web.WebRequest("show-" + type, base=request, id=item_id)
raise qm.web.HttpRedirect, request
--- 2292,2320 ----
redisplay = 1
if type is "test":
# Create a new test.
item = TestDescriptor(
! database,
test_id=item_id,
test_class_name=item_class_name,
arguments=arguments)
elif type is "resource":
# Create a new resource.
! item = ResourceDescriptor(database, item_id,
! item_class_name, arguments)
# If necessary, redisplay the form.
if redisplay:
request = qm.web.WebRequest("edit-" + type, base=request,
id=item_id)
return ShowItemPage(self, item, 1, is_new, type,
field_errors)(request)
# Store it in the database.
database.WriteExtension(item_id, item.GetItem())
# Redirect to a page that displays the newly-edited item.
request = qm.web.WebRequest("show-" + type, base=request, id=item_id)
raise qm.web.HttpRedirect, request
Index: share/dtml/attachment.dtml
===================================================================
RCS file: /home/qm/Repository/qm/share/dtml/attachment.dtml,v
retrieving revision 1.8
diff -c -5 -p -r1.8 attachment.dtml
*** share/dtml/attachment.dtml 9 May 2003 22:17:44 -0000 1.8
--- share/dtml/attachment.dtml 15 Jan 2004 05:59:32 -0000
***************
*** 149,159 ****
mime_type = document.upload_form.mime_type.value;
encoding = escape(description) + ";"
+ escape(mime_type) + ";"
+ escape(location) + ";"
! + escape(file_name);
summary = description + " (" + file_name
if(document.upload_form.detect_mime_type[1].checked)
summary += "; " + mime_type;
summary = summary + ")"
--- 149,160 ----
mime_type = document.upload_form.mime_type.value;
encoding = escape(description) + ";"
+ escape(mime_type) + ";"
+ escape(location) + ";"
! + escape(file_name) + ";"
! + escape("<dtml-var expr="attachment_store_id">");
summary = description + " (" + file_name
if(document.upload_form.detect_mime_type[1].checked)
summary += "; " + mime_type;
summary = summary + ")"
More information about the qmtest
mailing list