From 746a6cd52dac5f59c4b5707e3696621c87573dde Mon Sep 17 00:00:00 2001 From: Gaetan Delannay Date: Tue, 6 Jan 2015 21:13:30 +0100 Subject: [PATCH] [gen] Display action fields with layout 'buttons' in query results and tied objects for refs, besides transitions. [gen] Show delete button when allowed on the view layout. [gen] Improved method AbstractWrapper::createFrom. --- fields/action.py | 8 ++- fields/file.py | 50 ++++++++++---- fields/ref.py | 134 ++++++++++++++++++------------------ fields/workflow.py | 8 +-- gen/wrappers/ToolWrapper.py | 50 +++++++------- gen/wrappers/UserWrapper.py | 2 +- gen/wrappers/__init__.py | 50 ++++++++------ 7 files changed, 171 insertions(+), 131 deletions(-) diff --git a/fields/action.py b/fields/action.py index c19bbe5..0709b8e 100644 --- a/fields/action.py +++ b/fields/action.py @@ -30,21 +30,23 @@ class Action(Field):
- -
''') diff --git a/fields/file.py b/fields/file.py index 56cbf95..f7286c4 100644 --- a/fields/file.py +++ b/fields/file.py @@ -86,10 +86,10 @@ class FileInfo: self.uploadName = uploadName # The name of the uploaded file self.size = 0 # Its size, in bytes self.mimeType = None # Its MIME type - self.modified = None # The last modification date for this file. + self.modified = None # The last modification date for this file # Complete metadata if p_inDb is False if not inDb: - self.fsName = '' # Already included in self.fsPath. + self.fsName = '' # Already included in self.fsPath # We will not store p_inDb. Checking if self.fsName is the empty # string is equivalent. fileInfo = os.stat(self.fsPath) @@ -166,20 +166,25 @@ class FileInfo: '''Writes to the filesystem the p_fileObj file, that can be: - a Zope FileUpload (coming from a HTTP post); - a OFS.Image.File object (legacy within-ZODB file object); + - another ("not-in-DB") FileInfo instance; - a tuple (fileName, fileContent, mimeType) (see doc in method File.store below).''' # Determine p_fileObj's type fileType = fileObj.__class__.__name__ - # Set MIME type + # Determine the MIME type and the base name of the file to store if fileType == 'FileUpload': mimeType = self.getMimeTypeFromFileUpload(fileObj) + fileName = fileObj.filename elif fileType == 'File': mimeType = fileObj.content_type + fileName = fileObj.filename + elif fileType == 'FileInfo': + mimeType = fileObj.mimeType + fileName = fileObj.uploadName else: mimeType = fileObj[2] + fileName = fileObj[0] self.mimeType = mimeType or File.defaultMimeType - # Determine the original name of the file to store. - fileName= fileType.startswith('File') and fileObj.filename or fileObj[0] if not fileName: # Name it according to field name. Deduce file extension from the # MIME type. @@ -195,12 +200,12 @@ class FileInfo: fsName = osPathJoin(dbFolder, self.fsPath, self.fsName) f = file(fsName, 'wb') if fileType == 'FileUpload': - # Write the FileUpload instance on disk. + # Write the FileUpload instance on disk self.size = self.replicateFile(fileObj, f) elif fileType == 'File': - # Write the File instance on disk. + # Write the File instance on disk if fileObj.data.__class__.__name__ == 'Pdata': - # The file content is splitted in several chunks. + # The file content is splitted in several chunks f.write(fileObj.data.data) nextPart = fileObj.data.next while nextPart: @@ -210,10 +215,14 @@ class FileInfo: # Only one chunk f.write(fileObj.data) self.size = fileObj.size + elif fileType == 'FileInfo': + src = file(fileObj.fsPath, 'rb') + self.size = self.replicateFile(src, f) + src.close() else: - # Write fileObj[1] on disk. + # Write fileObj[1] on disk if fileObj[1].__class__.__name__ == 'file': - # It is an open file handler. + # It is an open file handler self.size = self.replicateFile(fileObj[1], f) else: # We have file content directly in fileObj[1] @@ -361,7 +370,17 @@ class File(Field): name = requestName or self.name return obj.REQUEST.get('%s_file' % name) - def getCopyValue(self, obj): raise Exception('Not implemented yet.') + def getCopyValue(self, obj): + '''Create a copy of the FileInfo instance stored for p_obj for this + field. This copy will contain the absolute path to the file on the + filesystem. This way, the file may be read independently from p_obj + (and copied somewhere else).''' + info = self.getValue(obj) + if not info: return + # Create a "not-in-DB", temporary FileInfo + return FileInfo(info.getFilePath(obj), inDb=False, + uploadName=info.uploadName) + def getDefaultLayouts(self): return {'view':'l-f','edit':'lrv-f'} def isEmptyValue(self, obj, value): @@ -405,13 +424,15 @@ class File(Field): f. a 3-tuple (fileName, fileContent, mimeType) where - fileName and fileContent have the same meaning than above; - mimeType is the MIME type of the file. + g. a FileInfo instance, that must be "not-in-DB", ie, with an + absolute path in attribute fsPath. ''' zobj = obj.o if value: # There is a new value to store. Get the folder on disk where to # store the new file. dbFolder, folder = zobj.getFsFolder(create=True) - # Remove the previous file if it existed. + # Remove the previous file if it existed info = getattr(obj.aq_base, self.name, None) if info: # The previous file can be a legacy File object in an old @@ -432,6 +453,9 @@ class File(Field): elif isinstance(value, basestring): # Case d info.copyFile(self.name, value, dbFolder) + elif isinstance(value, FileInfo): + # Case g + info.writeFile(self.name, value, dbFolder) else: # Cases e, f. Extract file name, content and MIME type. fileName = mimeType = None @@ -444,7 +468,7 @@ class File(Field): mimeType = mimeType or guessMimeType(fileName) info.writeFile(self.name, (fileName, fileContent, mimeType), dbFolder) - # Store the FileInfo instance in the database. + # Store the FileInfo instance in the database setattr(obj, self.name, info) else: # I store value "None", excepted if I find in the request the desire diff --git a/fields/ref.py b/fields/ref.py index a85be14..d5c1d88 100644 --- a/fields/ref.py +++ b/fields/ref.py @@ -77,65 +77,67 @@ class Ref(Field): # This PX displays icons for triggering actions on a given referenced object # (edit, delete, etc). pxObjectActions = Px(''' - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - :tied.pxTransitions
''') +
+ + + + + + + + + + + + + + + + + + + + + + + :tied.pxTransitions + + + + :field.pxView + +
''') # Displays the button allowing to add a new object through a Ref field, if # it has been declared as addable and if multiplicities allow it. @@ -797,7 +799,7 @@ class Ref(Field): UIDs, because m_store on the destination object can store tied objects based on such a list.''' res = getattr(obj.aq_base, self.name, ()) - # Return a copy: it can be dangerous to give the real database value. + # Return a copy: it can be dangerous to give the real database value if res: return list(res) def getXmlValue(self, obj, value): @@ -830,7 +832,7 @@ class Ref(Field): paginated = startNumber != None isSearch = False if 'masterValues' in req: - # Convert masterValue(s) from id(s) to real object(s). + # Convert masterValue(s) from id(s) to real object(s) masterValues = req['masterValues'].strip() if not masterValues: masterValues = None else: @@ -865,11 +867,11 @@ class Ref(Field): objects.objects = [o.appy() for o in objects.objects] else: objects = self.select(obj) - # Remove already linked objects if required. + # Remove already linked objects if required if removeLinked: uids = getattr(obj.o.aq_base, self.name, None) if uids: - # Browse objects in reverse order and remove linked objects. + # Browse objects in reverse order and remove linked objects if isSearch: objs = objects.objects else: objs = objects i = len(objs) - 1 @@ -884,7 +886,7 @@ class Ref(Field): if paginated and not isSearch: total = len(objects) objects = objects[startNumber:startNumber + self.maxPerPage] - # Return the result, wrapped in a SomeObjects instance if required. + # Return the result, wrapped in a SomeObjects instance if required if not someObjects: if isSearch: return objects.objects return objects @@ -909,7 +911,7 @@ class Ref(Field): for tied in objects: menuId = self.menuIdMethod(obj, tied) if menuId in menuIds: - # We have already encountered this menu. + # We have already encountered this menu menuIndex = menuIds[menuId] res[menuIndex].objects.append(tied) else: @@ -947,7 +949,7 @@ class Ref(Field): '''This method returns the index of the first linked object that must be shown, or None if all linked objects must be shown at once (it happens when p_render is "menus").''' - # When using 'menus' render mode, all linked objects must be shown. + # When using 'menus' render mode, all linked objects must be shown if render == 'menus': return # When using 'list' (=default) render mode, the index of the first # object to show is in the request. diff --git a/fields/workflow.py b/fields/workflow.py index 4b0c833..2231329 100644 --- a/fields/workflow.py +++ b/fields/workflow.py @@ -328,7 +328,7 @@ class Transition: # functions return True. hasRole = None for condition in self.condition: - # "Unwrap" role names from Role instances. + # "Unwrap" role names from Role instances if isinstance(condition, Role): condition = condition.name if isinstance(condition, basestring): # It is a role if hasRole == None: @@ -337,7 +337,7 @@ class Transition: hasRole = True else: # It is a method res = condition(wf, obj.appy()) - if not res: return res # False or a No instance. + if not res: return res # False or a No instance if hasRole != False: return True @@ -345,13 +345,13 @@ class Transition: '''Executes the action related to this transition.''' msg = '' obj = obj.appy() - wf = wf.__instance__ # We need the prototypical instance here. + wf = wf.__instance__ # We need the prototypical instance here if type(self.action) in (tuple, list): # We need to execute a list of actions for act in self.action: msgPart = act(wf, obj) if msgPart: msg += msgPart - else: # We execute a single action only. + else: # We execute a single action only msgPart = self.action(wf, obj) if msgPart: msg += msgPart return msg diff --git a/gen/wrappers/ToolWrapper.py b/gen/wrappers/ToolWrapper.py index e595b60..79ad8e9 100644 --- a/gen/wrappers/ToolWrapper.py +++ b/gen/wrappers/ToolWrapper.py @@ -365,30 +365,34 @@ class ToolWrapper(AbstractWrapper): if="sub">::zobj.highlight(sub) - - - - - - - - -
- + + - - - - - :targetObj.appy().pxTransitions
+ linkInPopup=inPopup or (target.target != '_self')" + target=":target.target" onclick=":target.openPopup" + href=":zobj.getUrl(mode='edit', page=zobj.getDefaultEditPage(), \ + nav=navInfo, inPopup=linkInPopup)"> + + + + + + :targetObj.appy().pxTransitions + + + + :field.pxView + + diff --git a/gen/wrappers/UserWrapper.py b/gen/wrappers/UserWrapper.py index 4fa696a..9755c57 100644 --- a/gen/wrappers/UserWrapper.py +++ b/gen/wrappers/UserWrapper.py @@ -304,7 +304,7 @@ class UserWrapper(AbstractWrapper): rq = self.request if (self.user == self) and hasattr(rq, 'userLogins'): return rq.userLogins - # Compute it. + # Compute it res = [group.login for group in self.groups] if not groupsOnly: res.append(self.login) return res diff --git a/gen/wrappers/__init__.py b/gen/wrappers/__init__.py index 0054582..31bdf77 100644 --- a/gen/wrappers/__init__.py +++ b/gen/wrappers/__init__.py @@ -30,7 +30,7 @@ class AbstractWrapper(object): '(%s).click()' % q(gotoName)"/>''') pxNavigationStrip = Px(''' @@ -469,7 +469,7 @@ class AbstractWrapper(object): @@ -479,26 +479,22 @@ class AbstractWrapper(object): onclick=":'goto(%s)' % q(zobj.getUrl(page=previousPage, \ inPopup=inPopup))"/> - - - - - - + + - @@ -538,12 +539,10 @@ class AbstractWrapper(object): onclick=":'goto(%s)' % q(zobj.getUrl(page=nextPage, \ inPopup=inPopup))"/> - :obj.pxTransitions -