[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:
Gaetan Delannay 2014-02-26 23:40:27 +01:00
parent be145be254
commit c002b5cb59
8 changed files with 71 additions and 21 deletions

View file

@ -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>&nbsp;&nbsp;- <a href=":downloadUrl">:value.uploadName</a>&nbsp;&nbsp;-
<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()

View file

@ -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'"

View file

@ -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.'''

View file

@ -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()

View file

@ -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

View file

@ -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>

View file

@ -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'])")

View file

@ -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()
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------