1.1 --- a/XSLForms/Fields.py Sat Nov 29 02:11:56 2008 +0100
1.2 +++ b/XSLForms/Fields.py Sat Mar 21 00:29:05 2009 +0100
1.3 @@ -5,7 +5,7 @@
1.4 Interpretation of field collections from sources such as HTTP request parameter
1.5 dictionaries.
1.6
1.7 -Copyright (C) 2005, 2006, 2007, 2008 Paul Boddie <paul@boddie.org.uk>
1.8 +Copyright (C) 2005, 2006, 2007, 2008, 2009 Paul Boddie <paul@boddie.org.uk>
1.9
1.10 This program is free software; you can redistribute it and/or modify it under
1.11 the terms of the GNU Lesser General Public License as published by the Free
1.12 @@ -59,6 +59,8 @@
1.13 except NameError:
1.14 from sets import Set as set
1.15
1.16 +FILE_NAMESPACE = "http://www.boddie.org.uk/ns/xmltools/file-upload"
1.17 +
1.18 class FieldsError(Exception):
1.19 pass
1.20
1.21 @@ -88,8 +90,14 @@
1.22
1.23 """
1.24 Complete the given 'documents' using the 'fields' items list.
1.25 +
1.26 + Include a special entry in 'documents' for the key None,
1.27 + referencing a list of file upload tuples of the form
1.28 + (filename, content).
1.29 """
1.30
1.31 + files = []
1.32 +
1.33 for field, value in fields:
1.34
1.35 # Ignore selectors.
1.36 @@ -119,15 +127,7 @@
1.37 if self.values_are_lists:
1.38 value = value[0]
1.39
1.40 - # Convert the value to Unicode if necessary.
1.41 -
1.42 - if type(value) == type(""):
1.43 - value = unicode(value, encoding=self.encoding)
1.44 -
1.45 - # Remove CR characters, ignoring non-textual parameters.
1.46 -
1.47 - if isinstance(value, (str, unicode)):
1.48 - node.setAttributeNS(EMPTY_NAMESPACE, t[0], value.replace("\r", ""))
1.49 + self._set_attribute(node, t[0], value, files)
1.50 break
1.51
1.52 elif len(t) == 2:
1.53 @@ -156,16 +156,33 @@
1.54 name = t[0]
1.55 for subvalue in values:
1.56 subnode = self._append_element(node, name)
1.57 + self._set_attribute(subnode, t[2], subvalue, files)
1.58
1.59 - # Convert the value to Unicode if necessary.
1.60 + documents[None] = files
1.61 +
1.62 + def _set_attribute(self, node, name, value, files):
1.63 +
1.64 + """
1.65 + Set an attribute on 'node' having the given 'name' and 'value', adding
1.66 + entries to the 'files' list if file upload fields are detected.
1.67 + """
1.68 +
1.69 + # Convert the value to Unicode if necessary.
1.70
1.71 - if type(subvalue) == type(""):
1.72 - subvalue = unicode(subvalue, encoding=self.encoding)
1.73 + if type(value) == type(""):
1.74 + value = unicode(value, encoding=self.encoding)
1.75 +
1.76 + # Remove CR characters, ignoring non-textual parameters.
1.77
1.78 - # Remove CR characters, ignoring non-textual parameters.
1.79 + if isinstance(value, (str, unicode)):
1.80 + node.setAttributeNS(EMPTY_NAMESPACE, name, value.replace("\r", ""))
1.81 +
1.82 + # Handle file uploads having certain attributes.
1.83
1.84 - if isinstance(subvalue, (str, unicode)):
1.85 - subnode.setAttributeNS(EMPTY_NAMESPACE, t[2], subvalue.replace("\r", ""))
1.86 + elif hasattr(value, "content") and hasattr(value, "filename"):
1.87 + node.setAttributeNS(FILE_NAMESPACE, "file:" + name, str(len(files)))
1.88 + node.setAttributeNS(EMPTY_NAMESPACE, name, value.filename)
1.89 + files.append((value.filename, value.content))
1.90
1.91 def complete_selectors(self, selectors, fields, documents, create):
1.92
1.93 @@ -466,6 +483,15 @@
1.94 parameters.append((parameter_name, value))
1.95 return FieldProcessor.get_selectors(self, parameters, self.documents, create)
1.96
1.97 + def get_files(self):
1.98 +
1.99 + """
1.100 + Get the uploaded file details as a list of tuples of the form
1.101 + (filename, content).
1.102 + """
1.103 +
1.104 + return self.documents.get(None, [])
1.105 +
1.106 def new_instance(self, name):
1.107
1.108 """
2.1 --- a/XSLForms/Resources/WebResources.py Sat Nov 29 02:11:56 2008 +0100
2.2 +++ b/XSLForms/Resources/WebResources.py Sat Mar 21 00:29:05 2009 +0100
2.3 @@ -3,7 +3,7 @@
2.4 """
2.5 Resources for use with WebStack.
2.6
2.7 -Copyright (C) 2005, 2006, 2007, 2008 Paul Boddie <paul@boddie.org.uk>
2.8 +Copyright (C) 2005, 2006, 2007, 2008, 2009 Paul Boddie <paul@boddie.org.uk>
2.9
2.10 This program is free software; you can redistribute it and/or modify it under
2.11 the terms of the GNU Lesser General Public License as published by the Free
2.12 @@ -104,6 +104,9 @@
2.13 not be overridden)
2.14 """
2.15
2.16 + EMPTY_NAMESPACE = XSLForms.Fields.EMPTY_NAMESPACE
2.17 + FILE_NAMESPACE = XSLForms.Fields.FILE_NAMESPACE
2.18 +
2.19 #path_encoding = "utf-8"
2.20 #encoding = "utf-8"
2.21
3.1 --- a/examples/Common/Candidate/Resources/candidate_display_template.xhtml Sat Nov 29 02:11:56 2008 +0100
3.2 +++ b/examples/Common/Candidate/Resources/candidate_display_template.xhtml Sat Mar 21 00:29:05 2009 +0100
3.3 @@ -20,6 +20,12 @@
3.4 </td>
3.5 </tr>
3.6 <tr>
3.7 + <th width="30%"></th>
3.8 + <td width="70%">
3.9 + <img template:if="@picture != ''" src="images/{@picture}" alt="{@picture}" />
3.10 + </td>
3.11 + </tr>
3.12 + <tr>
3.13 <th width="30%">Work status</th>
3.14 <td width="70%">
3.15 <span template:element="status" template:value="@value">...</span>
4.1 --- a/examples/Common/Candidate/Resources/candidate_template.xhtml Sat Nov 29 02:11:56 2008 +0100
4.2 +++ b/examples/Common/Candidate/Resources/candidate_template.xhtml Sat Mar 21 00:29:05 2009 +0100
4.3 @@ -7,7 +7,7 @@
4.4 <title>Candidate</title>
4.5 </head>
4.6 <body template:element="candidate">
4.7 -<form action="" method="POST">
4.8 +<form action="" method="POST" enctype="multipart/form-data">
4.9
4.10 <h1>Candidate Details</h1>
4.11
4.12 @@ -110,8 +110,22 @@
4.13 </td>
4.14 </tr>
4.15 <tr>
4.16 + <th width="30%">Picture</th>
4.17 + <td width="70%">
4.18 + <input template:attribute-field="new-picture" type="file" name="..." />
4.19 + <input template:attribute-field="picture" type="hidden" name="..." value="..." />
4.20 + </td>
4.21 + </tr>
4.22 + <tr>
4.23 <th width="30%"></th>
4.24 <td width="70%">
4.25 + <img template:if="@picture != ''" src="images/{@picture}" alt="{@picture}" />
4.26 + </td>
4.27 + </tr>
4.28 + <tr>
4.29 + <th width="30%"></th>
4.30 + <td width="70%">
4.31 + <input type="submit" name="update" value="Update" />
4.32 <input type="submit" name="show" value="Show..." />
4.33 <input type="submit" name="admin" value="Admin..." />
4.34 </td>
5.1 --- a/examples/Common/Candidate/__init__.py Sat Nov 29 02:11:56 2008 +0100
5.2 +++ b/examples/Common/Candidate/__init__.py Sat Mar 21 00:29:05 2009 +0100
5.3 @@ -12,7 +12,7 @@
5.4 # Site map imports.
5.5
5.6 from WebStack.Resources.ResourceMap import MapResource
5.7 -from WebStack.Resources.Selectors import EncodingSelector
5.8 +from WebStack.Resources.Selectors import EncodingSelector, StoreSelector
5.9 from WebStack.Resources.Static import DirectoryResource
5.10
5.11 # Configuration setting.
5.12 @@ -33,9 +33,6 @@
5.13 "admin" : input("admin_template.xhtml")
5.14 }
5.15
5.16 - def __init__(self, repository):
5.17 - self.repository = repository
5.18 -
5.19 def select_activity(self, trans, form):
5.20 form.set_activity("admin")
5.21
5.22 @@ -58,26 +55,30 @@
5.23 selectors = form.get_selectors()
5.24 if selectors.has_key("show"):
5.25 name = selectors["show"][0].getAttribute("name")
5.26 - trans.redirect(trans.encode_path(show_path) +
5.27 - "?name=%s" % trans.encode_path(name))
5.28 + if name:
5.29 + trans.redirect(trans.encode_path(show_path) +
5.30 + "?name=%s" % trans.encode_path(name))
5.31 elif selectors.has_key("edit"):
5.32 name = selectors["edit"][0].getAttribute("name")
5.33 - trans.redirect(trans.encode_path(edit_path) +
5.34 - "?name=%s" % trans.encode_path(name))
5.35 + if name:
5.36 + trans.redirect(trans.encode_path(edit_path) +
5.37 + "?name=%s" % trans.encode_path(name))
5.38
5.39 - # Add and remove elements according to the selectors found.
5.40 + # Add and remove elements (and documents) according to the selectors found.
5.41
5.42 + self.remove_documents(trans, selectors.get("remove"))
5.43 self.remove_elements(selectors.get("remove"))
5.44 self.add_elements(selectors.get("new"), "cv", "cvs")
5.45
5.46 def respond_to_document(self, trans, form):
5.47
5.48 admin = form.get_document()
5.49 + repository = trans.get_attributes()["store"]
5.50
5.51 # Synchronise the repository with the CVs found.
5.52
5.53 cvs = admin.xpath("/admin/cvs")[0]
5.54 - for key in self.repository.keys():
5.55 + for key in repository.keys():
5.56 if key.startswith("candidate-"):
5.57 name = key[len("candidate-"):]
5.58 # NOTE: Apostrophes not quoted.
5.59 @@ -87,15 +88,21 @@
5.60 cv.setAttribute("name", name)
5.61 cvs.appendChild(cv)
5.62 else:
5.63 - del self.repository[key]
5.64 + del repository[key]
5.65 +
5.66 + def remove_documents(self, trans, selected):
5.67 + repository = trans.get_attributes()["store"]
5.68 +
5.69 + for element in selected or []:
5.70 + name = element.getAttribute("name")
5.71 + docname = "candidate-%s" % name
5.72 + if repository.has_key(docname):
5.73 + del repository[docname]
5.74
5.75 class CandidateUtils:
5.76
5.77 "Methods used by candidate-related resources."
5.78
5.79 - def __init__(self, repository):
5.80 - self.repository = repository
5.81 -
5.82 def select_activity(self, trans, form):
5.83 form.set_activity("candidate")
5.84
5.85 @@ -103,16 +110,17 @@
5.86 documents = form.get_documents()
5.87 fields = trans.get_fields_from_path()
5.88 name = fields.get("name", [u"None"])[0]
5.89 + repository = trans.get_attributes()["store"]
5.90
5.91 # Ensure the presence of a document.
5.92
5.93 if documents.has_key("candidate"):
5.94 form.set_document(documents["candidate"])
5.95 else:
5.96 - if self.repository is None or not self.repository.has_key("candidate-%s" % name):
5.97 + if repository is None or not repository.has_key("candidate-%s" % name):
5.98 form.set_document(form.new_instance("candidate"))
5.99 else:
5.100 - form.set_document(libxml2dom.parseString(self.repository["candidate-%s" % name]))
5.101 + form.set_document(libxml2dom.parseString(repository["candidate-%s" % name]))
5.102
5.103 def init_document(self, trans, form):
5.104 status_xml = self.prepare_document("status")
5.105 @@ -162,9 +170,26 @@
5.106
5.107 candidate = form.get_document()
5.108 parameters = form.get_parameters()
5.109 + files = form.get_files()
5.110 fields = trans.get_fields_from_path()
5.111 name = fields.get("name", [u"None"])[0]
5.112
5.113 + repository = trans.get_attributes()["store"]
5.114 + image_repository = trans.get_attributes()["images"]
5.115 +
5.116 + # Find uploaded pictures and place them in the image repository.
5.117 +
5.118 + for element in candidate.xpath("//*[@new-picture]"):
5.119 + filename = element.getAttributeNS(self.EMPTY_NAMESPACE, "new-picture")
5.120 + index = int(element.getAttributeNS(self.FILE_NAMESPACE, "new-picture"))
5.121 +
5.122 + if filename.find(".") != -1:
5.123 + suffix = filename.split(".")[-1]
5.124 + saved_filename = "candidate-%s.%s" % (name, suffix)
5.125 + image_repository[saved_filename] = files[index][1] # content
5.126 + element.setAttribute("picture", saved_filename)
5.127 + break
5.128 +
5.129 # Get the "show" resource path.
5.130 # NOTE: This should be obtained from the site map.
5.131
5.132 @@ -178,7 +203,7 @@
5.133
5.134 # Save the candidate information.
5.135
5.136 - self.repository["candidate-%s" % name] = candidate.toString()
5.137 + repository["candidate-%s" % name] = candidate.toString()
5.138 trans.redirect(trans.encode_path(show_path) +
5.139 "?name=%s" % trans.encode_path(name))
5.140
5.141 @@ -188,7 +213,7 @@
5.142
5.143 # Save the candidate information.
5.144
5.145 - self.repository["candidate-%s" % name] = candidate.toString()
5.146 + repository["candidate-%s" % name] = candidate.toString()
5.147 trans.redirect(trans.encode_path(admin_path))
5.148
5.149 # Site map initialisation.
5.150 @@ -201,23 +226,37 @@
5.151 resource_dir = os.getcwd()
5.152 else:
5.153 resource_dir = resources(__file__)
5.154 +
5.155 + images_dir = os.path.join(resource_dir, "candidate-images")
5.156 +
5.157 repository = DirectoryRepository(os.path.join(resource_dir, "candidates"), fsencoding)
5.158 + image_repository = DirectoryRepository(images_dir, fsencoding)
5.159
5.160 # Get the main resource and the directory used by the application.
5.161
5.162 - candidate_resource = CandidateResource(repository)
5.163 - display_resource = DisplayResource(repository)
5.164 - admin_resource = AdminResource(repository)
5.165 + candidate_resource = CandidateResource()
5.166 + display_resource = DisplayResource()
5.167 + admin_resource = AdminResource()
5.168
5.169 # Make a simple Web site.
5.170
5.171 resource = MapResource({
5.172 "edit" : candidate_resource,
5.173 "show" : display_resource,
5.174 - "" : admin_resource
5.175 + "" : admin_resource,
5.176 + "images" : DirectoryResource(images_dir, {"png" : "image/png", "jpg" : "image/jpeg", "jpeg" : "image/jpeg"})
5.177 })
5.178
5.179 - return EncodingSelector(resource, encoding)
5.180 + # Return the site with some extra boilerplate.
5.181 +
5.182 + return \
5.183 + StoreSelector(
5.184 + StoreSelector(
5.185 + EncodingSelector(resource, encoding),
5.186 + repository),
5.187 + image_repository,
5.188 + "images"
5.189 + )
5.190
5.191 # Resource preparation ahead of time - useful for making installations.
5.192