# HG changeset patch # User Paul Boddie # Date 1237591745 -3600 # Node ID 8734ed455f859e44a68badd81ba570d1155b9f6c # Parent c35cc4e223bd9b1fef8398a67d84729dbeca1c7c Added support for file uploads, employing a special list of files as part of the forms dictionary, and using a file namespace in order to provide special attributes which refer to entries in the files list. Extended the candidate example to support picture uploads. diff -r c35cc4e223bd -r 8734ed455f85 XSLForms/Fields.py --- a/XSLForms/Fields.py Sat Nov 29 02:11:56 2008 +0100 +++ b/XSLForms/Fields.py Sat Mar 21 00:29:05 2009 +0100 @@ -5,7 +5,7 @@ Interpretation of field collections from sources such as HTTP request parameter dictionaries. -Copyright (C) 2005, 2006, 2007, 2008 Paul Boddie +Copyright (C) 2005, 2006, 2007, 2008, 2009 Paul Boddie This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free @@ -59,6 +59,8 @@ except NameError: from sets import Set as set +FILE_NAMESPACE = "http://www.boddie.org.uk/ns/xmltools/file-upload" + class FieldsError(Exception): pass @@ -88,8 +90,14 @@ """ Complete the given 'documents' using the 'fields' items list. + + Include a special entry in 'documents' for the key None, + referencing a list of file upload tuples of the form + (filename, content). """ + files = [] + for field, value in fields: # Ignore selectors. @@ -119,15 +127,7 @@ if self.values_are_lists: value = value[0] - # Convert the value to Unicode if necessary. - - if type(value) == type(""): - value = unicode(value, encoding=self.encoding) - - # Remove CR characters, ignoring non-textual parameters. - - if isinstance(value, (str, unicode)): - node.setAttributeNS(EMPTY_NAMESPACE, t[0], value.replace("\r", "")) + self._set_attribute(node, t[0], value, files) break elif len(t) == 2: @@ -156,16 +156,33 @@ name = t[0] for subvalue in values: subnode = self._append_element(node, name) + self._set_attribute(subnode, t[2], subvalue, files) - # Convert the value to Unicode if necessary. + documents[None] = files + + def _set_attribute(self, node, name, value, files): + + """ + Set an attribute on 'node' having the given 'name' and 'value', adding + entries to the 'files' list if file upload fields are detected. + """ + + # Convert the value to Unicode if necessary. - if type(subvalue) == type(""): - subvalue = unicode(subvalue, encoding=self.encoding) + if type(value) == type(""): + value = unicode(value, encoding=self.encoding) + + # Remove CR characters, ignoring non-textual parameters. - # Remove CR characters, ignoring non-textual parameters. + if isinstance(value, (str, unicode)): + node.setAttributeNS(EMPTY_NAMESPACE, name, value.replace("\r", "")) + + # Handle file uploads having certain attributes. - if isinstance(subvalue, (str, unicode)): - subnode.setAttributeNS(EMPTY_NAMESPACE, t[2], subvalue.replace("\r", "")) + elif hasattr(value, "content") and hasattr(value, "filename"): + node.setAttributeNS(FILE_NAMESPACE, "file:" + name, str(len(files))) + node.setAttributeNS(EMPTY_NAMESPACE, name, value.filename) + files.append((value.filename, value.content)) def complete_selectors(self, selectors, fields, documents, create): @@ -466,6 +483,15 @@ parameters.append((parameter_name, value)) return FieldProcessor.get_selectors(self, parameters, self.documents, create) + def get_files(self): + + """ + Get the uploaded file details as a list of tuples of the form + (filename, content). + """ + + return self.documents.get(None, []) + def new_instance(self, name): """ diff -r c35cc4e223bd -r 8734ed455f85 XSLForms/Resources/WebResources.py --- a/XSLForms/Resources/WebResources.py Sat Nov 29 02:11:56 2008 +0100 +++ b/XSLForms/Resources/WebResources.py Sat Mar 21 00:29:05 2009 +0100 @@ -3,7 +3,7 @@ """ Resources for use with WebStack. -Copyright (C) 2005, 2006, 2007, 2008 Paul Boddie +Copyright (C) 2005, 2006, 2007, 2008, 2009 Paul Boddie This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free @@ -104,6 +104,9 @@ not be overridden) """ + EMPTY_NAMESPACE = XSLForms.Fields.EMPTY_NAMESPACE + FILE_NAMESPACE = XSLForms.Fields.FILE_NAMESPACE + #path_encoding = "utf-8" #encoding = "utf-8" diff -r c35cc4e223bd -r 8734ed455f85 examples/Common/Candidate/Resources/candidate_display_template.xhtml --- a/examples/Common/Candidate/Resources/candidate_display_template.xhtml Sat Nov 29 02:11:56 2008 +0100 +++ b/examples/Common/Candidate/Resources/candidate_display_template.xhtml Sat Mar 21 00:29:05 2009 +0100 @@ -20,6 +20,12 @@ + + + {@picture} + + + Work status ... diff -r c35cc4e223bd -r 8734ed455f85 examples/Common/Candidate/Resources/candidate_template.xhtml --- a/examples/Common/Candidate/Resources/candidate_template.xhtml Sat Nov 29 02:11:56 2008 +0100 +++ b/examples/Common/Candidate/Resources/candidate_template.xhtml Sat Mar 21 00:29:05 2009 +0100 @@ -7,7 +7,7 @@ Candidate -
+

Candidate Details

@@ -110,8 +110,22 @@ + Picture + + + + + + + {@picture} + + + + + + diff -r c35cc4e223bd -r 8734ed455f85 examples/Common/Candidate/__init__.py --- a/examples/Common/Candidate/__init__.py Sat Nov 29 02:11:56 2008 +0100 +++ b/examples/Common/Candidate/__init__.py Sat Mar 21 00:29:05 2009 +0100 @@ -12,7 +12,7 @@ # Site map imports. from WebStack.Resources.ResourceMap import MapResource -from WebStack.Resources.Selectors import EncodingSelector +from WebStack.Resources.Selectors import EncodingSelector, StoreSelector from WebStack.Resources.Static import DirectoryResource # Configuration setting. @@ -33,9 +33,6 @@ "admin" : input("admin_template.xhtml") } - def __init__(self, repository): - self.repository = repository - def select_activity(self, trans, form): form.set_activity("admin") @@ -58,26 +55,30 @@ selectors = form.get_selectors() if selectors.has_key("show"): name = selectors["show"][0].getAttribute("name") - trans.redirect(trans.encode_path(show_path) + - "?name=%s" % trans.encode_path(name)) + if name: + trans.redirect(trans.encode_path(show_path) + + "?name=%s" % trans.encode_path(name)) elif selectors.has_key("edit"): name = selectors["edit"][0].getAttribute("name") - trans.redirect(trans.encode_path(edit_path) + - "?name=%s" % trans.encode_path(name)) + if name: + trans.redirect(trans.encode_path(edit_path) + + "?name=%s" % trans.encode_path(name)) - # Add and remove elements according to the selectors found. + # Add and remove elements (and documents) according to the selectors found. + self.remove_documents(trans, selectors.get("remove")) self.remove_elements(selectors.get("remove")) self.add_elements(selectors.get("new"), "cv", "cvs") def respond_to_document(self, trans, form): admin = form.get_document() + repository = trans.get_attributes()["store"] # Synchronise the repository with the CVs found. cvs = admin.xpath("/admin/cvs")[0] - for key in self.repository.keys(): + for key in repository.keys(): if key.startswith("candidate-"): name = key[len("candidate-"):] # NOTE: Apostrophes not quoted. @@ -87,15 +88,21 @@ cv.setAttribute("name", name) cvs.appendChild(cv) else: - del self.repository[key] + del repository[key] + + def remove_documents(self, trans, selected): + repository = trans.get_attributes()["store"] + + for element in selected or []: + name = element.getAttribute("name") + docname = "candidate-%s" % name + if repository.has_key(docname): + del repository[docname] class CandidateUtils: "Methods used by candidate-related resources." - def __init__(self, repository): - self.repository = repository - def select_activity(self, trans, form): form.set_activity("candidate") @@ -103,16 +110,17 @@ documents = form.get_documents() fields = trans.get_fields_from_path() name = fields.get("name", [u"None"])[0] + repository = trans.get_attributes()["store"] # Ensure the presence of a document. if documents.has_key("candidate"): form.set_document(documents["candidate"]) else: - if self.repository is None or not self.repository.has_key("candidate-%s" % name): + if repository is None or not repository.has_key("candidate-%s" % name): form.set_document(form.new_instance("candidate")) else: - form.set_document(libxml2dom.parseString(self.repository["candidate-%s" % name])) + form.set_document(libxml2dom.parseString(repository["candidate-%s" % name])) def init_document(self, trans, form): status_xml = self.prepare_document("status") @@ -162,9 +170,26 @@ candidate = form.get_document() parameters = form.get_parameters() + files = form.get_files() fields = trans.get_fields_from_path() name = fields.get("name", [u"None"])[0] + repository = trans.get_attributes()["store"] + image_repository = trans.get_attributes()["images"] + + # Find uploaded pictures and place them in the image repository. + + for element in candidate.xpath("//*[@new-picture]"): + filename = element.getAttributeNS(self.EMPTY_NAMESPACE, "new-picture") + index = int(element.getAttributeNS(self.FILE_NAMESPACE, "new-picture")) + + if filename.find(".") != -1: + suffix = filename.split(".")[-1] + saved_filename = "candidate-%s.%s" % (name, suffix) + image_repository[saved_filename] = files[index][1] # content + element.setAttribute("picture", saved_filename) + break + # Get the "show" resource path. # NOTE: This should be obtained from the site map. @@ -178,7 +203,7 @@ # Save the candidate information. - self.repository["candidate-%s" % name] = candidate.toString() + repository["candidate-%s" % name] = candidate.toString() trans.redirect(trans.encode_path(show_path) + "?name=%s" % trans.encode_path(name)) @@ -188,7 +213,7 @@ # Save the candidate information. - self.repository["candidate-%s" % name] = candidate.toString() + repository["candidate-%s" % name] = candidate.toString() trans.redirect(trans.encode_path(admin_path)) # Site map initialisation. @@ -201,23 +226,37 @@ resource_dir = os.getcwd() else: resource_dir = resources(__file__) + + images_dir = os.path.join(resource_dir, "candidate-images") + repository = DirectoryRepository(os.path.join(resource_dir, "candidates"), fsencoding) + image_repository = DirectoryRepository(images_dir, fsencoding) # Get the main resource and the directory used by the application. - candidate_resource = CandidateResource(repository) - display_resource = DisplayResource(repository) - admin_resource = AdminResource(repository) + candidate_resource = CandidateResource() + display_resource = DisplayResource() + admin_resource = AdminResource() # Make a simple Web site. resource = MapResource({ "edit" : candidate_resource, "show" : display_resource, - "" : admin_resource + "" : admin_resource, + "images" : DirectoryResource(images_dir, {"png" : "image/png", "jpg" : "image/jpeg", "jpeg" : "image/jpeg"}) }) - return EncodingSelector(resource, encoding) + # Return the site with some extra boilerplate. + + return \ + StoreSelector( + StoreSelector( + EncodingSelector(resource, encoding), + repository), + image_repository, + "images" + ) # Resource preparation ahead of time - useful for making installations.