ArchGenXML: Patch to support ordering of ReferenceFields created by associations
"Matt Hahnfeld" <[email protected]>
| Newsgroups | gmane.comp.web.zope.plone.archetypes.devel |
|---|---|
| Message-ID | <[email protected]> |
Hello all! The attached patch implements support for the "next_field" tagged value on any attribute or association end. This is one way to resolve the infamous issue in UML/ArchGenXML that doesn't allow reference fields created through associations to be ordered within the generated schema. Of course, ReferenceFields can be defined as attributes instead of associations, but then you lose the visual benefits of having your references appear on your UML diagram. When dealing with a large number of Archetypes-based content types, we've found that using associations is almost a necessity. This patch allows you to use associations and have them ordered into the rest of your fields. We've looked at many other options (including numeric ordering, using an ordered array of fields as a tagged value on the class, modifying argouml to support custom tags for ordering, using "stub" attributes to match up with associations, etc.) but we found this solution to be more logical, maintainable, and better supported by UML editors than the other solutions. So, here's an example: * I have two classes -- Page and Image (of course, image could be a stub class for the standard ATImage). I want each Page to contain a body (text), an Image from our images directory called "myimage" (reference), and an image caption called "caption" (string). I want the fields to appear in that order. * In my UML diagram, I would create my two classes (Page and Image) and add "body:text" and "caption:string" to my class attributes for Page. * I would add an association from Page to Image, and set its field/widget attributes at the endpoint closest to the Image class. * If I want my image field to appear after my body field, I would add the "next_field" tagged value to "body", and give it the value "myimage". * When I run ArchGenXML to generate my class, the three fields generated would be "body", then "myimage", then "caption". This is a patch to the current "trunk" revision of ArchetypesGenerator.py as of February 27, 2006 (revision 6053). I am looking for feedback at this point, and I would like to propose this patch, or something similar, for future inclusion into ArchGenXML. I'm patching against svn, so I should be able to provide up-to-date patches for future revisions of ArchetypesGenerator.py. I could also add prev_field support pretty easily if there is a desire for that. Thanks in advance for feedback/comments. Enjoy! Matt Hahnfeld [email protected]
next_field_support.patch
(application/octet-stream, 7.8 KB)
Index: ArchetypesGenerator.py
===================================================================
--- ArchetypesGenerator.py (revision 6053)
+++ ArchetypesGenerator.py (working copy)
@@ -1058,10 +1058,10 @@
return widgetcode
- def getFieldFormatted(self,name,fieldtype,map={},doc=None, indent_level=0, rawType='String'):
+ def getFieldFormatted(self,name,fieldtype,map={},doc=None, indent_level=0, rawType='String', array_field=False):
"""Return the formatted field definitions for the schema.
"""
-
+
log.debug("Trying to get formatted field. name='%s', fieldtype='%s', "
"doc='%s', rawType='%s'.", name, fieldtype, doc, rawType)
res = ''
@@ -1105,16 +1105,20 @@
res += '\n%s' % utils.indent('),', indent_level) + '\n'
+ if array_field:
+ res = "ArrayField(%s)," % utils.indent(res, 2)
+
return res
- def getFieldString(self, element, classelement, indent_level=0):
+ def getFieldSpec(self, element, classelement, indent_level=0):
"""Gets the schema field code."""
typename = element.type
ctype = self.coerceType(typename)
map = typeMap[ctype]['map'].copy()
- res = self.getFieldFormatted(element.getCleanName(),
- self.typeMap[ctype]['field'].copy(),
- map, indent_level)
+ res= {'name':element.getCleanName(),
+ 'fieldtype':self.typeMap[ctype]['field'].copy(),
+ 'map':map,
+ 'indent_level':indent_level}
return res
def addVocabulary(self, element, attr, map):
@@ -1153,7 +1157,7 @@
# end ATVM
- def getFieldStringFromAttribute(self, attr, classelement, indent_level=0):
+ def getFieldSpecFromAttribute(self, attr, classelement, indent_level=0):
"""Gets the schema field code."""
if not hasattr(attr, 'type') or attr.type == 'NoneType':
@@ -1220,17 +1224,17 @@
if map.has_key('validation_expression_errormsg'):
del map['validation_expression_errormsg']
- res = self.getFieldFormatted(attr.getName(), atype, map, doc,
- rawType=attr.getType(),
- indent_level=indent_level)
+ res={'name':attr.getName(),
+ 'fieldtype':atype,
+ 'map':map,
+ 'doc':doc,
+ 'indent_level':indent_level,
+ 'rawType':attr.getType(),
+ 'array_field':attr.getUpperBound() != 1}
- if attr.getUpperBound() != 1:
- utils.indent(res, 1)
- res = """ArrayField(%s),""" % utils.indent(res, 1)
-
return res
- def getFieldStringFromAssociation(self, rel, classelement, indent_level=0):
+ def getFieldSpecFromAssociation(self, rel, classelement, indent_level=0):
"""Return the schema field code."""
log.debug("Getting the field string from an association.")
@@ -1304,10 +1308,14 @@
% rel.getName()})
doc=rel.getDocumentation(striphtml=self.striphtml)
- res=self.getFieldFormatted(name, field, map, doc, indent_level)
+ res={'name':name,
+ 'fieldtype':field,
+ 'map':map,
+ 'doc':doc,
+ 'indent_level':indent_level}
return res
- def getFieldStringFromBackAssociation(self, rel, classelement, indent_level=0):
+ def getFieldSpecFromBackAssociation(self, rel, classelement, indent_level=0):
"""Gets the schema field code"""
multiValued = 0
obj = rel.fromEnd.obj
@@ -1361,9 +1369,35 @@
return None
doc = rel.getDocumentation(striphtml=self.striphtml)
- res = self.getFieldFormatted(name, field, map, doc, indent_level)
+ res={'name':name,
+ 'fieldtype':field,
+ 'map':map,
+ 'doc':doc,
+ 'indent_level':indent_level}
return res
+ def reorderFields(self, field_specs):
+ """ reorder fields according to the "next_field" tagged value """
+ # initialize a list to keep track of the ordering and a copy of our array
+ field_names = []
+ next_fields = []
+ for fs in field_specs:
+ field_names.append(fs['name'])
+ if 'map' in fs and 'next_field' in fs['map']:
+ next_fields.append((fs['name'], str(fs['map']['next_field']).strip('"')))
+ del fs['map']['next_field']
+ # figure out new ordering
+ for (first,second) in next_fields:
+ if second in field_names:
+ from_index = field_names.index(second)
+ temp = field_specs[from_index]
+ del field_names[from_index]
+ del field_specs[from_index]
+ to_index = field_names.index(first) + 1
+ field_names.insert(to_index,second)
+ field_specs.insert(to_index,temp)
+ return field_specs
+
# Generate get/set/add member functions.
def generateArcheSchema(self, element, base_schema, indent_level=0):
""" generates the Schema """
@@ -1413,6 +1447,7 @@
or fieldname, element, fieldname)
print >> outfile, SCHEMA_START
+ field_specs = []
aggregatedClasses = []
for attrDef in element.getAttributeDefs():
@@ -1421,8 +1456,8 @@
# continue
mappedName = utils.mapName(name)
- print >> outfile, self.getFieldStringFromAttribute(attrDef, element,
- indent_level=indent_level+1)
+ field_specs.append(self.getFieldSpecFromAttribute(attrDef, element,
+ indent_level=indent_level+1))
for child in element.getChildren():
name = child.getCleanName()
@@ -1433,8 +1468,8 @@
aggregatedClasses.append(str(child.getRef()))
if child.isIntrinsicType():
- print >> outfile, self.getFieldString(child, element,
- indent_level=indent_level+1)
+ field_specs.append(self.getFieldSpec(child, element,
+ indent_level=indent_level+1))
# only add reference fields if tgv generate_reference_fields
if utils.toBoolean(
@@ -1448,10 +1483,9 @@
#print 'generating from assoc'
if name in self.reservedAtts:
continue
- print >> outfile
- print >> outfile, self.getFieldStringFromAssociation(rel,
+ field_specs.append(self.getFieldSpecFromAssociation(rel,
element,
- indent_level=indent_level+1)
+ indent_level=indent_level+1))
#Back References
for rel in element.getToAssociations():
@@ -1460,13 +1494,16 @@
#print "backreference"
if name in self.reservedAtts:
continue
- fc=self.getFieldStringFromBackAssociation(rel,
+ fc=self.getFieldSpecFromBackAssociation(rel,
element,
indent_level=indent_level+1)
if fc:
- print >> outfile
- print >> outfile, fc
+ field_specs.append(fc)
+ self.reorderFields(field_specs)
+ for field_spec in field_specs:
+ print >> outfile, self.getFieldFormatted(**field_spec)
+
print >> outfile,'),'
marshaller=element.getTaggedValue('marshaller') or element.getTaggedValue('marshall')
if marshaller: