[gen] Added px.override allowing to modify PX code; bugfixes; added migration code for converting File instances to FileInfo instances.
This commit is contained in:
parent
be145be254
commit
c002b5cb59
|
@ -181,7 +181,7 @@ class File(Field):
|
|||
|
||||
pxView = pxCell = Px('''
|
||||
<x var="downloadUrl='%s/download?name=%s' % (zobj.absolute_url(), name);
|
||||
shownSize=value.getShownSize()">
|
||||
shownSize=value and value.getShownSize() or 0">
|
||||
<x if="value and not field.isImage">
|
||||
<a href=":downloadUrl">:value.uploadName</a> -
|
||||
<i class="discreet">:shownSize</i>
|
||||
|
@ -319,7 +319,11 @@ class File(Field):
|
|||
dbFolder, folder = zobj.getFsFolder(create=True)
|
||||
# Remove the previous file if it existed.
|
||||
info = getattr(obj.aq_base, self.name, None)
|
||||
if info: info.removeFile(dbFolder)
|
||||
if info:
|
||||
# The previous file can be a legacy File object in an old
|
||||
# database we are migrating.
|
||||
if isinstance(info, FileInfo): info.removeFile(dbFolder)
|
||||
else: delattr(obj, self.name)
|
||||
# Store the new file. As a preamble, create a FileInfo instance.
|
||||
info = FileInfo(folder)
|
||||
cfg = zobj.getProductConfig()
|
||||
|
|
|
@ -306,7 +306,7 @@ class UiGroup:
|
|||
<!-- Group content -->
|
||||
<div var="display=expanded and 'display:block' or 'display:none'"
|
||||
id=":field.labelId" style=":'padding-left: 10px; %s' % display">
|
||||
<x for="searches in field.widgets">
|
||||
<x for="searches in field.elements">
|
||||
<x for="elem in searches">
|
||||
<!-- An inner group within this group -->
|
||||
<x if="elem.type == 'group'"
|
||||
|
|
|
@ -40,6 +40,11 @@ from appy.fields.workflow import *
|
|||
from appy.gen.layout import Table
|
||||
from appy.gen.utils import No
|
||||
|
||||
# Make the following classes available here: people may need to monkey-patch
|
||||
# some PXs on thoses classes.
|
||||
from appy.gen.wrappers import AbstractWrapper as BaseObject
|
||||
from appy.gen.wrappers.ToolWrapper import ToolWrapper as BaseTool
|
||||
|
||||
class Import:
|
||||
'''Used for describing the place where to find the data to use for creating
|
||||
an object.'''
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# ------------------------------------------------------------------------------
|
||||
import time
|
||||
from appy.fields.file import FileInfo
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class Migrator:
|
||||
|
@ -11,13 +12,36 @@ class Migrator:
|
|||
self.app = installer.app
|
||||
self.tool = self.app.config.appy()
|
||||
|
||||
@staticmethod
|
||||
def migrateFileFields(obj):
|
||||
'''Ensures all file fields on p_obj are FileInfo instances.'''
|
||||
migrated = 0 # Count the number of migrated fields
|
||||
for field in obj.fields:
|
||||
if field.type != 'File': continue
|
||||
oldValue = getattr(obj, field.name)
|
||||
if oldValue and not isinstance(oldValue, FileInfo):
|
||||
# A legacy File object. Convert it to a FileInfo instance and
|
||||
# extract the binary to the filesystem.
|
||||
setattr(obj, field.name, oldValue)
|
||||
migrated += 1
|
||||
return migrated
|
||||
|
||||
def migrateTo_0_9_0(self):
|
||||
'''Migrates this DB to Appy 0.9.x.'''
|
||||
pass
|
||||
# Put all binaries to the filesystem
|
||||
tool = self.tool
|
||||
tool.log('Migrating file fields...')
|
||||
context = {'migrate': self.migrateFileFields, 'nb': 0}
|
||||
for className in tool.o.getAllClassNames():
|
||||
tool.compute(className, context=context, noSecurity=True,
|
||||
expression="ctx['nb'] += ctx['migrate'](obj)")
|
||||
tool.log('Migrated %d File field(s).' % context['nb'])
|
||||
|
||||
def run(self):
|
||||
def run(self, force=False):
|
||||
'''Executes a migration when relevant, or do it for sure if p_force is
|
||||
True.'''
|
||||
appyVersion = self.tool.appyVersion
|
||||
if not appyVersion or (appyVersion < '0.9.0'):
|
||||
if force or not appyVersion or (appyVersion < '0.9.0'):
|
||||
# Migration is required.
|
||||
startTime = time.time()
|
||||
self.migrateTo_0_9_0()
|
||||
|
|
|
@ -470,6 +470,11 @@ class ToolMixin(BaseMixin):
|
|||
if wrapper: return zopeClass.wrapperClass
|
||||
else: return zopeClass.wrapperClass.__bases__[-1]
|
||||
|
||||
def getAllClassNames(self):
|
||||
'''Returns the name of all classes within this app, including default
|
||||
Appy classes (Tool, Translation, Page, etc).'''
|
||||
return self.getProductConfig().allClassNames + [self.__class__.__name__]
|
||||
|
||||
def getCreateMeans(self, klass):
|
||||
'''Gets the different ways objects of p_klass can be created (via a web
|
||||
form, by importing external data, etc). Result is a dict whose keys
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
# ------------------------------------------------------------------------------
|
||||
import os.path, time
|
||||
import appy
|
||||
from appy.gen.mail import sendMail
|
||||
from appy.shared.utils import executeCommand
|
||||
from appy.gen.wrappers import AbstractWrapper
|
||||
from appy.px import Px
|
||||
|
||||
from appy.gen.mail import sendMail
|
||||
from appy.gen.wrappers import AbstractWrapper
|
||||
from appy.shared.utils import executeCommand
|
||||
# ------------------------------------------------------------------------------
|
||||
class ToolWrapper(AbstractWrapper):
|
||||
|
||||
|
@ -233,7 +232,7 @@ class ToolWrapper(AbstractWrapper):
|
|||
</x>
|
||||
|
||||
<!-- Predefined searches -->
|
||||
<x for="search in searchInfo.searches">
|
||||
<x for="search in searchInfo.searches" var2="field=search">
|
||||
<x if="search.type == 'group'">:search.px</x>
|
||||
<x if="search.type != 'group'">:search.pxView</x>
|
||||
</x>
|
||||
|
|
|
@ -123,7 +123,7 @@ class UserWrapper(AbstractWrapper):
|
|||
# Browse all objects of the database and update potential local roles
|
||||
# that referred to the old login.
|
||||
context = {'nb': 0, 'old': oldLogin, 'new': newLogin}
|
||||
for className in self.o.getProductConfig().allClassNames:
|
||||
for className in self.tool.o.getAllClassNames():
|
||||
self.compute(className, context=context, noSecurity=True,
|
||||
expression="ctx['nb'] += obj.o.applyUserIdChange(" \
|
||||
"ctx['old'], ctx['new'])")
|
||||
|
|
|
@ -42,10 +42,22 @@ class Px:
|
|||
else:
|
||||
self.content = content
|
||||
# It this content a complete XML file, or just some part of it?
|
||||
if partial:
|
||||
self.partial = partial
|
||||
# Is this PX based on a template PX?
|
||||
self.template = template
|
||||
self.hook = hook
|
||||
# Is there some (XML, XHTML...) prologue to dump?
|
||||
self.prologue = prologue
|
||||
# Will the result be unicode or str?
|
||||
self.unicode = unicode
|
||||
self.parse()
|
||||
|
||||
def parse(self):
|
||||
'''Parses self.content and create the structure corresponding to this
|
||||
PX.'''
|
||||
if self.partial:
|
||||
# Surround the partial chunk with a root tag: it must be valid XML.
|
||||
self.content = '<x>%s</x>' % self.content
|
||||
self.partial = partial
|
||||
# Create a PX parser
|
||||
self.parser = PxParser(PxEnvironment(), self)
|
||||
# Parses self.content (a PX code in a string) with self.parser, to
|
||||
|
@ -55,13 +67,6 @@ class Px:
|
|||
except xml.sax.SAXParseException, spe:
|
||||
self.completeErrorMessage(spe)
|
||||
raise spe
|
||||
# Is this PX based on a template PX?
|
||||
self.template = template
|
||||
self.hook = hook
|
||||
# Is there some (XML, XHTML...) prologue to dump?
|
||||
self.prologue = prologue
|
||||
# Will the result be unicode or str?
|
||||
self.unicode = unicode
|
||||
|
||||
def completeErrorMessage(self, parsingError):
|
||||
'''A p_parsingError occurred. Complete the error message with the
|
||||
|
@ -108,4 +113,12 @@ class Px:
|
|||
if not self.unicode:
|
||||
res = res.encode('utf-8')
|
||||
return res
|
||||
|
||||
def override(self, content, partial=True):
|
||||
'''Overrides the content of this PX with a new p_content (as a
|
||||
string).'''
|
||||
self.partial = partial
|
||||
self.content = content
|
||||
# Parse again, with new content.
|
||||
self.parse()
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
Loading…
Reference in a new issue