Added a method for computing things on large sets of objects and added the possibility to edit through-the-web in the flavour the import path for classes whose instances can be imported from a file system.

This commit is contained in:
Gaetan Delannay 2010-01-18 15:12:22 +01:00
parent 08c5abdd49
commit 24c0dee96c
7 changed files with 92 additions and 22 deletions

View file

@ -11,7 +11,8 @@ from utils import stringify
import appy.gen
import appy.gen.descriptors
from appy.gen.po import PoMessage
from appy.gen import Date, String, State, Transition, Type, Search, Selection
from appy.gen import Date, String, State, Transition, Type, Search, \
Selection, Import
from appy.gen.utils import GroupDescr, PageDescr, produceNiceMessage, \
sequenceTypes
TABS = 4 # Number of blanks in a Python indentation.
@ -486,6 +487,20 @@ class ArchetypesClassDescriptor(ClassDescriptor):
res = self.isFolder(theClass.__bases__[0])
return res
def getCreateMean(self, type='Import'):
'''Returns the mean for this class that corresponds to p_type, or
None if the class does not support this create mean.'''
if not self.klass.__dict__.has_key('create'): return None
else:
means = self.klass.create
if not means: return None
if not isinstance(means, tuple) and not isinstance(means, list):
means = [means]
for mean in means:
exec 'found = isinstance(mean, %s)' % type
if found: return mean
return None
@staticmethod
def getSearches(klass):
'''Returns the list of searches that are defined on this class.'''

View file

@ -615,6 +615,9 @@ class Generator(AbstractGenerator):
Flavour._appy_addQueryResultColumns(classDescr)
# Add the search-related fields.
Flavour._appy_addSearchRelatedFields(classDescr)
importMean = classDescr.getCreateMean('Import')
if importMean:
Flavour._appy_addImportRelatedFields(classDescr)
Flavour._appy_addWorkflowFields(self.flavourDescr)
Flavour._appy_addWorkflowFields(self.podTemplateDescr)
# Generate the flavour class and related i18n messages

View file

@ -1,4 +1,5 @@
# ------------------------------------------------------------------------------
import os, os.path
import appy.gen
from appy.gen import Type
from appy.gen.plone25.mixins import AbstractMixin
@ -148,4 +149,26 @@ class FlavourMixin(AbstractMixin):
dAttr = self._appy_getTypeAsDict(attrName, attr, appyClass)
res.append((attrName, dAttr))
return res
def getImportElements(self, contentType):
'''Returns the list of elements that can be imported from p_path for
p_contentType.'''
tool = self.getParentNode()
appyClass = tool.getAppyClass(contentType)
importParams = tool.getCreateMeans(appyClass)['import']
onElement = importParams['onElement'].__get__('')
sortMethod = importParams['sort']
if sortMethod: sortMethod = sortMethod.__get__('')
elems = []
importPath = getattr(self, 'importPathFor%s' % contentType)
for elem in os.listdir(importPath):
elemFullPath = os.path.join(importParams['path'], elem)
elemInfo = onElement(elemFullPath)
if elemInfo:
elemInfo.insert(0, elemFullPath) # To the result, I add the full
# path of the elem, which will not be shown.
elems.append(elemInfo)
if sortMethod:
elems = sortMethod(elems)
return [importParams['headers'], elems]
# ------------------------------------------------------------------------------

View file

@ -364,26 +364,6 @@ class ToolMixin(AbstractMixin):
res[means.id] = means.__dict__
return res
def getImportElements(self, contentType):
'''Returns the list of elements that can be imported from p_path for
p_contentType.'''
appyClass = self.getAppyClass(contentType)
importParams = self.getCreateMeans(appyClass)['import']
onElement = importParams['onElement'].__get__('')
sortMethod = importParams['sort']
if sortMethod: sortMethod = sortMethod.__get__('')
elems = []
for elem in os.listdir(importParams['path']):
elemFullPath = os.path.join(importParams['path'], elem)
elemInfo = onElement(elemFullPath)
if elemInfo:
elemInfo.insert(0, elemFullPath) # To the result, I add the full
# path of the elem, which will not be shown.
elems.append(elemInfo)
if sortMethod:
elems = sortMethod(elems)
return [importParams['headers'], elems]
def onImportObjects(self):
'''This method is called when the user wants to create objects from
external data.'''

View file

@ -206,6 +206,18 @@ class Flavour(ModelClass):
page='userInterface', group=classDescr.klass.__name__)
klass._appy_addField(fieldName, fieldType, classDescr)
@classmethod
def _appy_addImportRelatedFields(klass, classDescr):
'''Adds, for class p_classDescr, attributes related to the import
functionality for class p_classDescr.'''
className = classDescr.name
# Field that defines the path of the files to import.
fieldName = 'importPathFor%s' % className
defValue = classDescr.getCreateMean('Import').path
fieldType = String(page='data', multiplicity=(1,1), default=defValue,
group=classDescr.klass.__name__)
klass._appy_addField(fieldName, fieldType, classDescr)
@classmethod
def _appy_addWorkflowFields(klass, classDescr):
'''Adds, for a given p_classDescr, the workflow-related fields.'''

View file

@ -16,7 +16,8 @@
tal:define="appFolder context/getParentNode;
contentType request/type_name;
tool python: portal.get('portal_%s' % appFolder.id.lower());
importElems python: tool.getImportElements(contentType);
flavour python: tool.getFlavour(contentType);
importElems python: flavour.getImportElements(contentType);
global allAreImported python:True">
<div metal:use-macro="here/skyn/macros/macros/pagePrologue"/>

View file

@ -295,6 +295,42 @@ class AbstractWrapper:
if res: return res._len # It is a LazyMap instance
else: return 0
def compute(self, klass, context=None, expression=None, noSecurity=False,
**fields):
'''This method, like m_search and m_count above, performs a query on
objects of p_klass. But in this case, instead of returning a list of
matching objects (like m_search) or counting elements (like p_count),
it evaluates, on every matching object, a Python p_expression (which
may be an expression or a statement), and returns, if needed, a
result. The result may be initialized through parameter p_context.
p_expression is evaluated with 2 variables in its context: "obj"
which is the currently walked object, instance of p_klass, and "ctx",
which is the context as initialized (or not) by p_context. p_context
may be used as
(1) a variable that is updated on every call to produce a result;
(2) an input variable;
(3) both.
The method returns p_context, modified or not by evaluation of
p_expression on every matching object.
When you need to perform an action or computation on a lot of
objects, use this method instead of doing things like
"for obj in self.search(MyClass,...)"
'''
flavour = self.flavour
contentType = flavour.o.getPortalType(klass)
search = Search('customSearch', **fields)
# Initialize the context variable "ctx"
ctx = context
for brain in self.tool.o.executeQuery(contentType, flavour.number, \
search=search, brainsOnly=True, noSecurity=noSecurity):
# Get the Appy object from the brain
obj = brain.getObject().appy()
exec expression
return ctx
def reindex(self):
'''Asks a direct object reindexing. In most cases you don't have to
reindex objects "manually" with this method. When an object is