[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('''
|
pxView = pxCell = Px('''
|
||||||
<x var="downloadUrl='%s/download?name=%s' % (zobj.absolute_url(), name);
|
<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">
|
<x if="value and not field.isImage">
|
||||||
<a href=":downloadUrl">:value.uploadName</a> -
|
<a href=":downloadUrl">:value.uploadName</a> -
|
||||||
<i class="discreet">:shownSize</i>
|
<i class="discreet">:shownSize</i>
|
||||||
|
@ -319,7 +319,11 @@ class File(Field):
|
||||||
dbFolder, folder = zobj.getFsFolder(create=True)
|
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)
|
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.
|
# Store the new file. As a preamble, create a FileInfo instance.
|
||||||
info = FileInfo(folder)
|
info = FileInfo(folder)
|
||||||
cfg = zobj.getProductConfig()
|
cfg = zobj.getProductConfig()
|
||||||
|
|
|
@ -306,7 +306,7 @@ class UiGroup:
|
||||||
<!-- Group content -->
|
<!-- Group content -->
|
||||||
<div var="display=expanded and 'display:block' or 'display:none'"
|
<div var="display=expanded and 'display:block' or 'display:none'"
|
||||||
id=":field.labelId" style=":'padding-left: 10px; %s' % display">
|
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">
|
<x for="elem in searches">
|
||||||
<!-- An inner group within this group -->
|
<!-- An inner group within this group -->
|
||||||
<x if="elem.type == 'group'"
|
<x if="elem.type == 'group'"
|
||||||
|
|
|
@ -40,6 +40,11 @@ from appy.fields.workflow import *
|
||||||
from appy.gen.layout import Table
|
from appy.gen.layout import Table
|
||||||
from appy.gen.utils import No
|
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:
|
class Import:
|
||||||
'''Used for describing the place where to find the data to use for creating
|
'''Used for describing the place where to find the data to use for creating
|
||||||
an object.'''
|
an object.'''
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
import time
|
import time
|
||||||
|
from appy.fields.file import FileInfo
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
class Migrator:
|
class Migrator:
|
||||||
|
@ -11,13 +12,36 @@ class Migrator:
|
||||||
self.app = installer.app
|
self.app = installer.app
|
||||||
self.tool = self.app.config.appy()
|
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):
|
def migrateTo_0_9_0(self):
|
||||||
'''Migrates this DB to Appy 0.9.x.'''
|
'''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
|
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.
|
# Migration is required.
|
||||||
startTime = time.time()
|
startTime = time.time()
|
||||||
self.migrateTo_0_9_0()
|
self.migrateTo_0_9_0()
|
||||||
|
|
|
@ -470,6 +470,11 @@ class ToolMixin(BaseMixin):
|
||||||
if wrapper: return zopeClass.wrapperClass
|
if wrapper: return zopeClass.wrapperClass
|
||||||
else: return zopeClass.wrapperClass.__bases__[-1]
|
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):
|
def getCreateMeans(self, klass):
|
||||||
'''Gets the different ways objects of p_klass can be created (via a web
|
'''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
|
form, by importing external data, etc). Result is a dict whose keys
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
import os.path, time
|
import os.path, time
|
||||||
import appy
|
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.px import Px
|
||||||
|
from appy.gen.mail import sendMail
|
||||||
|
from appy.gen.wrappers import AbstractWrapper
|
||||||
|
from appy.shared.utils import executeCommand
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
class ToolWrapper(AbstractWrapper):
|
class ToolWrapper(AbstractWrapper):
|
||||||
|
|
||||||
|
@ -233,7 +232,7 @@ class ToolWrapper(AbstractWrapper):
|
||||||
</x>
|
</x>
|
||||||
|
|
||||||
<!-- Predefined searches -->
|
<!-- 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.px</x>
|
||||||
<x if="search.type != 'group'">:search.pxView</x>
|
<x if="search.type != 'group'">:search.pxView</x>
|
||||||
</x>
|
</x>
|
||||||
|
|
|
@ -123,7 +123,7 @@ class UserWrapper(AbstractWrapper):
|
||||||
# Browse all objects of the database and update potential local roles
|
# Browse all objects of the database and update potential local roles
|
||||||
# that referred to the old login.
|
# that referred to the old login.
|
||||||
context = {'nb': 0, 'old': oldLogin, 'new': newLogin}
|
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,
|
self.compute(className, context=context, noSecurity=True,
|
||||||
expression="ctx['nb'] += obj.o.applyUserIdChange(" \
|
expression="ctx['nb'] += obj.o.applyUserIdChange(" \
|
||||||
"ctx['old'], ctx['new'])")
|
"ctx['old'], ctx['new'])")
|
||||||
|
|
|
@ -42,10 +42,22 @@ class Px:
|
||||||
else:
|
else:
|
||||||
self.content = content
|
self.content = content
|
||||||
# It this content a complete XML file, or just some part of it?
|
# 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.
|
# Surround the partial chunk with a root tag: it must be valid XML.
|
||||||
self.content = '<x>%s</x>' % self.content
|
self.content = '<x>%s</x>' % self.content
|
||||||
self.partial = partial
|
|
||||||
# Create a PX parser
|
# Create a PX parser
|
||||||
self.parser = PxParser(PxEnvironment(), self)
|
self.parser = PxParser(PxEnvironment(), self)
|
||||||
# Parses self.content (a PX code in a string) with self.parser, to
|
# Parses self.content (a PX code in a string) with self.parser, to
|
||||||
|
@ -55,13 +67,6 @@ class Px:
|
||||||
except xml.sax.SAXParseException, spe:
|
except xml.sax.SAXParseException, spe:
|
||||||
self.completeErrorMessage(spe)
|
self.completeErrorMessage(spe)
|
||||||
raise 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):
|
def completeErrorMessage(self, parsingError):
|
||||||
'''A p_parsingError occurred. Complete the error message with the
|
'''A p_parsingError occurred. Complete the error message with the
|
||||||
|
@ -108,4 +113,12 @@ class Px:
|
||||||
if not self.unicode:
|
if not self.unicode:
|
||||||
res = res.encode('utf-8')
|
res = res.encode('utf-8')
|
||||||
return res
|
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