Checkpoint - nothing really works yet, but I'm getting scared of not having this checked in anywhere. :)
svn path=/plone.dexterity/trunk/; revision=21105
This commit is contained in:
commit
b2b6db5cda
|
@ -0,0 +1,4 @@
|
|||
Introduction
|
||||
============
|
||||
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
Changelog
|
||||
=========
|
||||
|
||||
1.0a1 - Unreleased
|
||||
----------------
|
||||
|
||||
* Initial release
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
plone.dexterity Installation
|
||||
==========================
|
||||
|
||||
To install plone.dexterity into the global Python environment (or a workingenv),
|
||||
using a traditional Zope 2 instance, you can do this:
|
||||
|
||||
* When you're reading this you have probably already run
|
||||
``easy_install plone.dexterity``. Find out how to install setuptools
|
||||
(and EasyInstall) here:
|
||||
http://peak.telecommunity.com/DevCenter/EasyInstall
|
||||
|
||||
* Create a file called ``plone.dexterity-configure.zcml`` in the
|
||||
``/path/to/instance/etc/package-includes`` directory. The file
|
||||
should only contain this::
|
||||
|
||||
<include package="plone.dexterity" />
|
||||
|
||||
|
||||
Alternatively, if you are using zc.buildout and the plone.recipe.zope2instance
|
||||
recipe to manage your project, you can do this:
|
||||
|
||||
* Add ``plone.dexterity`` to the list of eggs to install, e.g.:
|
||||
|
||||
[buildout]
|
||||
...
|
||||
eggs =
|
||||
...
|
||||
plone.dexterity
|
||||
|
||||
* Tell the plone.recipe.zope2instance recipe to install a ZCML slug:
|
||||
|
||||
[instance]
|
||||
recipe = plone.recipe.zope2instance
|
||||
...
|
||||
zcml =
|
||||
plone.dexterity
|
||||
|
||||
* Re-run buildout, e.g. with:
|
||||
|
||||
$ ./bin/buildout
|
||||
|
||||
You can skip the ZCML slug if you are going to explicitly include the package
|
||||
from another package's configure.zcml file.
|
|
@ -0,0 +1,222 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
|
@ -0,0 +1,16 @@
|
|||
plone.dexterity is copyright Martin Aspeli
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston,
|
||||
MA 02111-1307 USA.
|
|
@ -0,0 +1,6 @@
|
|||
# See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages
|
||||
try:
|
||||
__import__('pkg_resources').declare_namespace(__name__)
|
||||
except ImportError:
|
||||
from pkgutil import extend_path
|
||||
__path__ = extend_path(__path__, __name__)
|
|
@ -0,0 +1,66 @@
|
|||
Dexterity To-Do List
|
||||
====================
|
||||
|
||||
General
|
||||
-------
|
||||
|
||||
- Create a buildout with:
|
||||
- plone.alterego
|
||||
- plone.behavior
|
||||
- plone.folder
|
||||
- plone.supermodel
|
||||
- plone.dexterity
|
||||
|
||||
plone.supermodel
|
||||
----------------
|
||||
|
||||
- Support for widget hints for z3c.forms
|
||||
|
||||
plone.dexterity
|
||||
---------------
|
||||
|
||||
- Known issues:
|
||||
- Factory is not properly setting interfaces - alsoProvides() does not seem to stick
|
||||
|
||||
- Rectify atrocious lack of tests
|
||||
- want to use mocker for most tests
|
||||
- end-to-end integration tests for each type (see examples below)
|
||||
|
||||
- FTI needs to provide view, edit actions as standard
|
||||
- FTI needs to provide view, edit, default view aliases as standard
|
||||
|
||||
- Factory needs to initialise instance security based on schema
|
||||
- Add view needs to be protected by fti.add_permission dynamically
|
||||
|
||||
- Support type and FTI migration
|
||||
- Support runtime schema changes (including fields removed and changed)
|
||||
|
||||
- Use widget hints from plone.supermodel in add and edit forms
|
||||
- Support fields from behaviors in add/edit forms
|
||||
|
||||
- Need to cache model/schema more effectively
|
||||
|
||||
- Flesh out examples and possibly add convenience APIs for:
|
||||
- Type installed with GS, no filesystem component
|
||||
- Type installed with GS, filesystem interface
|
||||
- Type installed with GS, filesystem interface and type class
|
||||
- Type installed with GS, filesystem interface, type class and views
|
||||
|
||||
plone.app.dexterity
|
||||
-------------------
|
||||
|
||||
- Better views for IDexterityItem and IDexterityContainer
|
||||
- Provide optional standard Ploneish metadata fieldsets for add/edit forms
|
||||
- Make content types support title-to-id, perhaps via behaviors
|
||||
|
||||
- UI for building schemata
|
||||
- UI for view customisation
|
||||
|
||||
Integration concerns
|
||||
--------------------
|
||||
|
||||
- Need a reference engine (plone.app.relations)
|
||||
- Need working versioning with version-on-save (CMFEditions policy)
|
||||
- Need working content staging (iterate needs to stop using AT references)
|
||||
- Need working link integrity (ditto for plone.linkintegrity)
|
||||
- Need inline validation of z3c.form forms
|
|
@ -0,0 +1,4 @@
|
|||
import zope.i18nmessageid
|
||||
MessageFactory = zope.i18nmessageid.MessageFactory("plone.dexterity")
|
||||
|
||||
import schema
|
|
@ -0,0 +1,7 @@
|
|||
from plone.supermodel import xml_schema as xml_schema_
|
||||
from plone.dexterity.content import Item, Container
|
||||
|
||||
def xml_schema(filename, schema=u"", policy=u"dexterity", _frame=2):
|
||||
return xml_schema_(filename, schema, policy, _frame=+1)
|
||||
|
||||
__all__ = ('xml_schema', 'Item', 'Container', )
|
|
@ -0,0 +1,22 @@
|
|||
from zope.interface import implements
|
||||
from zope.component import adapts, getUtility
|
||||
|
||||
from plone.behavior.interfaces import IBehaviorAssignable
|
||||
|
||||
from plone.dexterity.interfaces import IDexterityFTI
|
||||
|
||||
class DexterityBehaviorAssignable(object):
|
||||
"""Support plone.behavior behaviors stored in the FTI
|
||||
"""
|
||||
|
||||
implements(IBehaviorAssignable)
|
||||
adapts(IDexterityFTI)
|
||||
|
||||
def __init__(self, context):
|
||||
self.fti = getUtility(IDexterityFTI, name=context.portal_type)
|
||||
|
||||
def supports(self, behavior_interface):
|
||||
return behavior_interface.__identifier__ in self.fti.behaviors
|
||||
|
||||
def enumerate_behaviors(self):
|
||||
return tuple(self.fti.behaviors)
|
|
@ -0,0 +1,75 @@
|
|||
from persistent import Persistent
|
||||
from zope.interface import implements
|
||||
from zope.component import adapts, getUtility, createObject
|
||||
|
||||
from zope.app.container.interfaces import IAdding
|
||||
|
||||
from zope.publisher.interfaces.browser import IBrowserView
|
||||
from zope.publisher.interfaces.browser import IBrowserRequest
|
||||
|
||||
from z3c.form import form, field, button, group, subform, adding
|
||||
from plone.z3cform import base
|
||||
|
||||
from plone.dexterity.interfaces import IDexterityFTI
|
||||
from plone.dexterity import MessageFactory as _
|
||||
|
||||
from Acquisition import aq_inner
|
||||
from AccessControl import Unauthorized
|
||||
from AccessControl import getSecurityManager
|
||||
|
||||
# TODO: Add view needs to be protected by fti.add_permission dynamically
|
||||
|
||||
class AddViewFactory(Persistent):
|
||||
"""Factory for add views - will be registered as a local adapter factory
|
||||
"""
|
||||
|
||||
implements(IBrowserView)
|
||||
adapts(IAdding, IBrowserRequest)
|
||||
|
||||
def __init__(self, portal_type):
|
||||
self.portal_type = portal_type
|
||||
|
||||
def __call__(self, context, request):
|
||||
return DefaultAddView(context, request, self.portal_type)
|
||||
|
||||
class DefaultAddForm(adding.AddForm):
|
||||
|
||||
def __init__(self, context, request, portal_type):
|
||||
super(DefaultAddForm, self).__init__(context, request)
|
||||
self.portal_type = portal_type
|
||||
|
||||
@property
|
||||
def fields(self):
|
||||
# TODO: Support plone.behavior-provided fields
|
||||
fti = getUtility(IDexterityFTI, name=self.portal_type)
|
||||
schema = fti.lookup_schema()
|
||||
return field.Fields(schema)
|
||||
|
||||
def create(self, data):
|
||||
fti = getUtility(IDexterityFTI, name=self.portal_type)
|
||||
content = createObject(fti.factory)
|
||||
form.applyChanges(self, content, data)
|
||||
return content
|
||||
|
||||
class DefaultAddView(base.FormWrapper):
|
||||
|
||||
def __init__(self, context, request, portal_type, form=None):
|
||||
super(DefaultAddView, self).__init__(context, request)
|
||||
|
||||
fti = getUtility(IDexterityFTI, name=portal_type)
|
||||
self.__name__ = fti.factory
|
||||
|
||||
if form is None:
|
||||
form = DefaultAddForm
|
||||
|
||||
self.form = form
|
||||
self.portal_type = portal_type
|
||||
|
||||
def render_form(self):
|
||||
return self.form(self.context.aq_inner, self.request, self.portal_type)()
|
||||
|
||||
@property
|
||||
def label(self):
|
||||
fti = getUtility(IDexterityFTI, name=self.portal_type)
|
||||
type_name = fti.title
|
||||
return _(u"Add ${name}", mapping={'name': type_name})
|
|
@ -0,0 +1,23 @@
|
|||
<configure
|
||||
xmlns="http://namespaces.zope.org/zope"
|
||||
xmlns:browser="http://namespaces.zope.org/browser"
|
||||
i18n_domain="plone.dexterity">
|
||||
|
||||
<!-- Standard view -->
|
||||
<browser:page
|
||||
for="..interfaces.IDexterityContent"
|
||||
name="view"
|
||||
class=".view.DefaultView"
|
||||
template="item.pt"
|
||||
permission="zope2.View"
|
||||
/>
|
||||
|
||||
<!-- Standard edit view -->
|
||||
<browser:page
|
||||
for="..interfaces.IDexterityContent"
|
||||
name="edit"
|
||||
class=".edit.DefaultEditView"
|
||||
permission="cmf.ModifyPortalContent"
|
||||
/>
|
||||
|
||||
</configure>
|
|
@ -0,0 +1,27 @@
|
|||
from zope.component import getUtility
|
||||
|
||||
from z3c.form import form, field, button, group, subform
|
||||
from plone.z3cform import base
|
||||
|
||||
from plone.dexterity.interfaces import IDexterityFTI
|
||||
from plone.dexterity import MessageFactory as _
|
||||
|
||||
class DefaultEditForm(form.EditForm):
|
||||
|
||||
@property
|
||||
def fields(self):
|
||||
# TODO: Support plone.behavior-provided fields
|
||||
portal_type = self.context.portal_type
|
||||
fti = getUtility(IDexterityFTI, name=portal_type)
|
||||
schema = fti.lookup_schema()
|
||||
return field.Fields(schema)
|
||||
|
||||
class DefaultEditView(base.FormWrapper):
|
||||
form = DefaultEditForm
|
||||
|
||||
@property
|
||||
def label(self):
|
||||
portal_type = self.context.portal_type
|
||||
fti = getUtility(IDexterityFTI, name=portal_type)
|
||||
type_name = fti.title
|
||||
label = _(u"Edit ${name}", mapping={'name': type_name})
|
|
@ -0,0 +1,26 @@
|
|||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
|
||||
xmlns:tal="http://xml.zope.org/namespaces/tal"
|
||||
xmlns:metal="http://xml.zope.org/namespaces/metal"
|
||||
xmlns:i18n="http://xml.zope.org/namespaces/i18n"
|
||||
lang="en"
|
||||
metal:use-macro="context/main_template/macros/master"
|
||||
i18n:domain="plone.dexterity">
|
||||
<body>
|
||||
|
||||
<metal:main fill-slot="main">
|
||||
|
||||
<h1 class="documentFirstHeading" tal:content="context/Title" />
|
||||
|
||||
<p class="documentDescription" tal:content="context/Description" />
|
||||
|
||||
<div tal:repeat="item view/fields">
|
||||
<label tal:attributes="for item/id"
|
||||
tal:content="for/title" />
|
||||
<div tal:content="item/value" />
|
||||
</div>
|
||||
|
||||
</metal:main>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
from zope.schema import getFieldsInOrder
|
||||
from zope.component import getUtility
|
||||
|
||||
from plone.dexterity.interfaces import IDexterityFTI
|
||||
|
||||
from Acquisition import aq_inner
|
||||
from Products.Five.browser import BrowserView
|
||||
|
||||
class DefaultView(BrowserView):
|
||||
|
||||
def fields(self, ignored=['title', 'description']):
|
||||
"""Get a list of tuples of the fields and their value
|
||||
"""
|
||||
|
||||
context = aq_inner(self.context)
|
||||
|
||||
fti = getUtility(IDexterityFTI, name=context.portal_type)
|
||||
schema = fti.lookup_schema()
|
||||
|
||||
for name, field in getFieldsInOrder(schema):
|
||||
if name not in ignored:
|
||||
field = field.bind(context)
|
||||
yield dict(id=field.__name__,
|
||||
title=field.title,
|
||||
description=field.description,
|
||||
value=field.get(context))
|
|
@ -0,0 +1,51 @@
|
|||
<configure
|
||||
xmlns="http://namespaces.zope.org/zope"
|
||||
xmlns:five="http://namespaces.zope.org/five"
|
||||
i18n_domain="plone.dexterity">
|
||||
|
||||
<include package="plone.behavior" />
|
||||
<include package="plone.folder" />
|
||||
<include package="plone.supermodel" />
|
||||
<include package="plone.z3cform" />
|
||||
|
||||
<include package=".browser" />
|
||||
<include package=".fti" />
|
||||
|
||||
<!-- Re-index content when it's modified -->
|
||||
<subscriber
|
||||
for=".interfaces.IDexterityContent
|
||||
zope.lifecycleevent.interfaces.IObjectModifiedEvent"
|
||||
handler=".content.reindexOnModify"
|
||||
/>
|
||||
|
||||
<!-- Support for dynamic schemata -->
|
||||
|
||||
<utility
|
||||
factory=".schema.DexteritySchemaPolicy"
|
||||
name="dexterity"
|
||||
/>
|
||||
|
||||
<utility
|
||||
factory=".schema.SchemaModuleFactory"
|
||||
name="plone.dexterity.schema.generated"
|
||||
/>
|
||||
|
||||
<!-- Support for plone.behavior behaviors -->
|
||||
<adapter factory=".behavior.DexterityBehaviorAssignable" />
|
||||
|
||||
<!-- Register the content classes -->
|
||||
<five:registerClass
|
||||
class=".content.Item"
|
||||
meta_type="Dexterity Item"
|
||||
permission="cmf.AddPortalContent"
|
||||
addview="dexterity.content"
|
||||
/>
|
||||
|
||||
<five:registerClass
|
||||
class=".content.Container"
|
||||
meta_type="Dexterity Container"
|
||||
permission="cmf.AddPortalContent"
|
||||
addview="dexterity.content"
|
||||
/>
|
||||
|
||||
</configure>
|
|
@ -0,0 +1,58 @@
|
|||
from zope.interface import implements
|
||||
|
||||
from plone.dexterity.interfaces import IDexterityItem
|
||||
from plone.dexterity.interfaces import IDexterityContainer
|
||||
|
||||
from zope.app.container.contained import Contained
|
||||
|
||||
from Products.CMFCore.PortalContent import PortalContent
|
||||
from Products.CMFCore.CMFCatalogAware import CMFCatalogAware
|
||||
|
||||
from Products.CMFDefault.DublinCore import DefaultDublinCoreImpl
|
||||
|
||||
from plone.folder.ordered import OrderedBTreeFolderBase
|
||||
|
||||
# XXX: It'd be nice to reduce the number of base classes here
|
||||
|
||||
class Item(PortalContent, DefaultDublinCoreImpl, Contained):
|
||||
"""A non-containerish, CMFish item
|
||||
"""
|
||||
|
||||
implements(IDexterityItem)
|
||||
isPrincipiaFolderish = 0
|
||||
|
||||
# portal_type must be set by factory or derived class
|
||||
portal_type = None
|
||||
|
||||
def __init__(self, id=None, **kwargs):
|
||||
PortalContent.__init__(self, id, **kwargs)
|
||||
DefaultDublinCoreImpl.__init__(self, **kwargs)
|
||||
|
||||
if id is not None:
|
||||
self.id = id
|
||||
|
||||
class Container(CMFCatalogAware, OrderedBTreeFolderBase, PortalContent, DefaultDublinCoreImpl, Contained):
|
||||
"""Base class for folderish items
|
||||
"""
|
||||
|
||||
implements(IDexterityContainer)
|
||||
isPrincipiaFolderish = 1
|
||||
|
||||
# portal_type must be set by factory or derived class
|
||||
portal_type = None
|
||||
|
||||
def __init__(self, id=None, **kwargs):
|
||||
OrderedBTreeFolderBase.__init__(self, id, **kwargs)
|
||||
DefaultDublinCoreImpl.__init__(self, **kwargs)
|
||||
|
||||
if id is not None:
|
||||
self.id = id
|
||||
|
||||
def reindexOnModify(content, event):
|
||||
"""When an object is modified, re-index it in the catalog
|
||||
"""
|
||||
|
||||
if event.object is not content:
|
||||
return
|
||||
|
||||
content.reindexObject(idxs=getattr(event, 'descriptions', []))
|
|
@ -0,0 +1,81 @@
|
|||
from persistent import Persistent
|
||||
|
||||
from zope.interface import implements
|
||||
from zope.interface import alsoProvides
|
||||
from zope.interface import implementedBy
|
||||
from zope.interface.declarations import Implements
|
||||
|
||||
from zope.component import getUtility
|
||||
from zope.component.factory import Factory
|
||||
|
||||
from zope.schema import getFieldsInOrder
|
||||
|
||||
from plone.dexterity.interfaces import IDexterityFTI
|
||||
from plone.dexterity.interfaces import IDexterityFactory
|
||||
|
||||
from Products.CMFCore.utils import getToolByName
|
||||
from Products.GenericSetup.utils import _resolveDottedName
|
||||
|
||||
class DexterityFactory(Persistent, Factory):
|
||||
"""A factory for
|
||||
"""
|
||||
|
||||
implements(IDexterityFactory)
|
||||
|
||||
def __init__(self, portal_type):
|
||||
self.portal_type = portal_type
|
||||
|
||||
@property
|
||||
def title(self):
|
||||
fti = getUtility(IDexterityFTI, name=self.portal_type)
|
||||
return fti.title
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
fti = getUtility(IDexterityFTI, name=self.portal_type)
|
||||
return fti.description
|
||||
|
||||
def __call__(self, *args, **kw):
|
||||
|
||||
|
||||
import pdb ; pdb.set_trace( )
|
||||
|
||||
fti = getUtility(IDexterityFTI, name=self.portal_type)
|
||||
|
||||
klass = _resolveDottedName(fti.klass)
|
||||
if klass is None or not callable(klass):
|
||||
raise ValueError("Content class %s set for type %s is not valid" % (fti.klass, self.portal_type))
|
||||
|
||||
try:
|
||||
obj = klass()
|
||||
except TypeError:
|
||||
raise ValueError("Content class %s set for type %s does not have a no-args constructor" % (fti.klass, self.portal_type))
|
||||
|
||||
# Set portal_type if not set
|
||||
if not getattr(obj, 'portal_type', None):
|
||||
obj.portal_type = self.portal_type
|
||||
|
||||
# Get schema interface
|
||||
|
||||
schema = fti.lookup_schema()
|
||||
alsoProvides(obj, schema)
|
||||
|
||||
# Initialise fields from the schema onto the type
|
||||
for name, field in getFieldsInOrder(schema):
|
||||
if not hasattr(obj, name):
|
||||
field = field.bind(obj)
|
||||
field.set(obj, field.default)
|
||||
|
||||
|
||||
# TODO: Initialise security
|
||||
|
||||
return obj
|
||||
|
||||
def getInterfaces(self):
|
||||
fti = getUtility(IDexterityFTI, name=self.portal_type)
|
||||
spec = Implements(fti.lookup_schema())
|
||||
spec.__name__ = self.portal_type
|
||||
return spec
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s for %s>' %(self.__class__.__name__, self.portal_type)
|
|
@ -0,0 +1,46 @@
|
|||
<h1 tal:replace="structure context/manage_page_header">PAGE HEADER</h1>
|
||||
<h2 tal:define="form_title view/title"
|
||||
tal:replace="structure context/manage_form_title">FORM TITLE</h2>
|
||||
|
||||
<p class="form-help" tal:content="view/description">DESCRIPTION TEXT.</p>
|
||||
|
||||
<form action="." method="post"
|
||||
tal:attributes="action request/ACTUAL_URL">
|
||||
<table cellspacing="0" cellpadding="2" border="0">
|
||||
<tr>
|
||||
<td>
|
||||
<div class="form-label">ID</div>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" name="add_input_name" size="40" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr tal:condition="view/getProfileInfos">
|
||||
<td>
|
||||
<div class="form-label">Presettings</div>
|
||||
</td>
|
||||
<td>
|
||||
<select name="settings_id">
|
||||
<option value="" selected="selected">(None)</option>
|
||||
<optgroup label="PROFILE_TITLE"
|
||||
tal:repeat="profile view/getProfileInfos"
|
||||
tal:attributes="label profile/title">
|
||||
<option value="SETTINGS_ID"
|
||||
tal:repeat="obj_id profile/obj_ids"
|
||||
tal:attributes="value string:${profile/id}/${obj_id}"
|
||||
tal:content="obj_id">OBJ ID</option></optgroup>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
</td>
|
||||
<td>
|
||||
<input class="form-element" type="submit" name="submit_add" value="Add" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
|
||||
<h1 tal:replace="structure context/manage_page_footer">PAGE FOOTER</h1>
|
|
@ -0,0 +1,18 @@
|
|||
from Products.CMFCore.browser.typeinfo import FactoryTypeInformationAddView
|
||||
|
||||
from plone.dexterity.fti.dynamic import DynamicFTI
|
||||
from plone.dexterity.fti.concrete import ConcreteFTI
|
||||
|
||||
class DynamicFTIAddView(FactoryTypeInformationAddView):
|
||||
"""Add view for the Dynamic FTI type
|
||||
"""
|
||||
|
||||
klass = DynamicFTI
|
||||
description = u'Factory Type Information for Dynamic Dexterity Content Types'
|
||||
|
||||
class ConcreteFTIAddView(FactoryTypeInformationAddView):
|
||||
"""Add view for the Concrete FTI type
|
||||
"""
|
||||
|
||||
klass = ConcreteFTI
|
||||
description = u'Factory Type Information for Concrete Dexterity Content Types'
|
|
@ -0,0 +1,124 @@
|
|||
from zope.interface import implements, noLongerProvides
|
||||
from zope.lifecycleevent import modified
|
||||
|
||||
from plone.dexterity.interfaces import IDexterityFTI
|
||||
from plone.dexterity.interfaces import ITemporarySchema
|
||||
|
||||
import plone.dexterity.schema
|
||||
from plone.dexterity import utils
|
||||
|
||||
from AccessControl import getSecurityManager
|
||||
from Products.CMFDynamicViewFTI import fti
|
||||
|
||||
class DexterityFTI(fti.DynamicViewTypeInformation):
|
||||
"""A Dexterity FTI
|
||||
"""
|
||||
|
||||
implements(IDexterityFTI)
|
||||
meta_type = "Dexterity FTI"
|
||||
|
||||
_properties = fti.DynamicViewTypeInformation._properties + (
|
||||
{ 'id': 'add_permission',
|
||||
'type': 'selection',
|
||||
'select_variable': 'possible_permissions',
|
||||
'mode': 'w',
|
||||
'label': 'Add permission',
|
||||
'description': 'Permission needed to be able to add content of this type'
|
||||
},
|
||||
{ 'id': 'klass',
|
||||
'type': 'string',
|
||||
'mode': 'w',
|
||||
'label': 'Content type class',
|
||||
'description': 'Dotted name to the class that contains the content type'
|
||||
},
|
||||
{ 'id': 'behaviors',
|
||||
'type': 'lines',
|
||||
'mode': 'w',
|
||||
'label': 'Behaviors',
|
||||
'description': 'Named of enabled behaviors type'
|
||||
},
|
||||
)
|
||||
|
||||
add_permission = u""
|
||||
klass = u""
|
||||
behaviors = []
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DexterityFTI, self).__init__(*args, **kwargs)
|
||||
self.behaviors = [] # don't bleed
|
||||
|
||||
# Tie the factory to the portal_type name - one less thing to have to set
|
||||
@property
|
||||
def factory(self):
|
||||
return self.getId()
|
||||
|
||||
def lookup_schema(self):
|
||||
# TODO: Cache schema, invalidate when FTI modified
|
||||
|
||||
schema_name = utils.portal_type_to_schema_name(self.getId())
|
||||
schema = getattr(plone.dexterity.schema.generated, schema_name)
|
||||
|
||||
if ITemporarySchema.providedBy(schema):
|
||||
try:
|
||||
model = self.lookup_model()
|
||||
except KeyError:
|
||||
raise ValueError(u"Model for %s does not contain a default schema" % (self.getId()))
|
||||
except Exception, e:
|
||||
raise ValueError(u"Error loading model for %s: %s" % (self.getId(), str(e)))
|
||||
|
||||
utils.sync_schema(model['schemata'][u""], schema)
|
||||
noLongerProvides(schema, ITemporarySchema)
|
||||
|
||||
return schema
|
||||
|
||||
def lookup_model(self):
|
||||
raise NotImplemented
|
||||
|
||||
# Make sure we get an event when the FTI is modifieid
|
||||
|
||||
def manage_editProperties(self, REQUEST=None):
|
||||
"""Gotta love Zope 2
|
||||
"""
|
||||
page = super(DexterityFTI, self).manage_editProperties(REQUEST)
|
||||
modified(self)
|
||||
return page
|
||||
|
||||
def manage_changeProperties(self, REQUEST=None, **kw):
|
||||
"""Gotta love Zope 2
|
||||
"""
|
||||
page = super(DexterityFTI, self).manage_changeProperties(REQUEST, **kw)
|
||||
modified(self)
|
||||
return page
|
||||
|
||||
# Allow us to specify a particular add permission rather than rely on ones
|
||||
# stored in meta types that we don't have anyway
|
||||
|
||||
def isConstructionAllowed(self, container):
|
||||
if not self.add_permission:
|
||||
return False
|
||||
return getSecurityManager().checkPermission(self.add_permission, container)
|
||||
|
||||
|
||||
def _fix_properties(class_, ignored=['product', 'content_meta_type', 'factory']):
|
||||
"""Remove properties with the given ids, and ensure that later properties
|
||||
override earlier ones with the same id
|
||||
"""
|
||||
properties = []
|
||||
processed = set()
|
||||
|
||||
for item in reversed(class_._properties):
|
||||
item = item.copy()
|
||||
|
||||
if item['id'] in processed:
|
||||
continue
|
||||
|
||||
# Ignore some fields
|
||||
if item['id'] in ignored:
|
||||
continue
|
||||
|
||||
properties.append(item)
|
||||
processed.add('id')
|
||||
|
||||
class_._properties = tuple(reversed(properties))
|
||||
|
||||
_fix_properties(DexterityFTI)
|
|
@ -0,0 +1,35 @@
|
|||
from zope.interface import implements
|
||||
|
||||
from plone.dexterity.interfaces import IConcreteFTI
|
||||
from plone.dexterity.fti.base import DexterityFTI, _fix_properties
|
||||
|
||||
from Products.GenericSetup.utils import _resolveDottedName
|
||||
|
||||
class ConcreteFTI(DexterityFTI):
|
||||
"""A Dexterity FTI that is connected to a filesystem type
|
||||
"""
|
||||
|
||||
implements(IConcreteFTI)
|
||||
|
||||
_properties = DexterityFTI._properties + (
|
||||
{ 'id': 'schema',
|
||||
'type': 'string',
|
||||
'mode': 'w',
|
||||
'label': 'Schema',
|
||||
'description': "Dotted name to the interface describing content type's schema"
|
||||
},
|
||||
|
||||
)
|
||||
|
||||
schema = u""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ConcreteFTI, self).__init__(*args, **kwargs)
|
||||
|
||||
def lookup_model(self):
|
||||
schema = _resolveDottedName(self.schema)
|
||||
if schema is None:
|
||||
raise ValueError(u"Schema %s set for type %s cannot be resolved" % (self.schema, self.getId()))
|
||||
return dict(schemata={u"": schema}, widgets={})
|
||||
|
||||
_fix_properties(ConcreteFTI)
|
|
@ -0,0 +1,60 @@
|
|||
<configure
|
||||
xmlns="http://namespaces.zope.org/zope"
|
||||
xmlns:browser="http://namespaces.zope.org/browser"
|
||||
xmlns:five="http://namespaces.zope.org/five"
|
||||
i18n_domain="plone.dexterity">
|
||||
|
||||
<browser:page
|
||||
for="zope.app.container.interfaces.IAdding"
|
||||
name="plone.dexterity.dynamicfti"
|
||||
template="addview.pt"
|
||||
class=".addview.DynamicFTIAddView"
|
||||
permission="cmf.ManagePortal"
|
||||
/>
|
||||
<browser:page
|
||||
for="zope.app.container.interfaces.IAdding"
|
||||
name="plone.dexterity.concretefti"
|
||||
template="addview.pt"
|
||||
class=".addview.ConcreteFTIAddView"
|
||||
permission="cmf.ManagePortal"
|
||||
/>
|
||||
|
||||
<!-- Register the FTI classes -->
|
||||
|
||||
<five:registerClass
|
||||
class=".dynamic.DynamicFTI"
|
||||
meta_type="Dynamic Dexterity FTI"
|
||||
addview="plone.dexterity.dynamicfti"
|
||||
permission="cmf.ManagePortal"
|
||||
global="False"
|
||||
/>
|
||||
|
||||
<five:registerClass
|
||||
class=".concrete.ConcreteFTI"
|
||||
meta_type="Concrete Dexterity FTI"
|
||||
addview="plone.dexterity.concretefti"
|
||||
permission="cmf.ManagePortal"
|
||||
global="False"
|
||||
/>
|
||||
|
||||
<!-- Register FTI add/remove/modify handlers -->
|
||||
|
||||
<subscriber
|
||||
for="..interfaces.IDexterityFTI
|
||||
zope.app.container.interfaces.IObjectAddedEvent"
|
||||
handler=".events.fti_added"
|
||||
/>
|
||||
|
||||
<subscriber
|
||||
for="..interfaces.IDexterityFTI
|
||||
zope.app.container.interfaces.IObjectRemovedEvent"
|
||||
handler=".events.fti_removed"
|
||||
/>
|
||||
|
||||
<subscriber
|
||||
for="..interfaces.IDexterityFTI
|
||||
zope.app.container.interfaces.IObjectMovedEvent"
|
||||
handler=".events.fti_renamed"
|
||||
/>
|
||||
|
||||
</configure>
|
|
@ -0,0 +1,65 @@
|
|||
import os.path
|
||||
|
||||
from zope.interface import implements
|
||||
|
||||
from plone.dexterity.interfaces import IDynamicFTI
|
||||
from plone.dexterity.fti.base import DexterityFTI, _fix_properties
|
||||
|
||||
from plone.supermodel import load_string, load_file
|
||||
|
||||
class DynamicFTI(DexterityFTI):
|
||||
"""A Dexterity FTI that is managed entirely through-the-web
|
||||
"""
|
||||
|
||||
implements(IDynamicFTI)
|
||||
|
||||
_properties = DexterityFTI._properties + (
|
||||
{ 'id': 'model_source',
|
||||
'type': 'text',
|
||||
'mode': 'w',
|
||||
'label': 'Model source',
|
||||
'description': "XML source for the type's model. Note that this takes " +
|
||||
"precendence over any model file."
|
||||
},
|
||||
{ 'id': 'model_file',
|
||||
'type': 'string',
|
||||
'mode': 'w',
|
||||
'label': 'Model file',
|
||||
'description': "Path to file containing the schema model. This can be " +
|
||||
"relative to a package, e.g. 'my.package:myschema.xml'."
|
||||
},
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DynamicFTI, self).__init__(*args, **kwargs)
|
||||
|
||||
self.model_source = ""
|
||||
self.model_file = ""
|
||||
|
||||
def lookup_model(self):
|
||||
# TODO: Cache model, invalidate when FTI modified
|
||||
model = None
|
||||
|
||||
if self.model_source:
|
||||
model = load_string(self.model_source, policy=u"dexterity")
|
||||
elif self.model_file:
|
||||
colons = self.model_file.count(':')
|
||||
model_file = self.model_file
|
||||
|
||||
# We have a package and not an absolute Windows path
|
||||
if colons == 1 and self.model_file[1:3] != ':\\':
|
||||
package, filename = self.model_file.split(':')
|
||||
try:
|
||||
mod = __import__(package)
|
||||
except ImportError:
|
||||
raise ValueError(u"Invalid package %s specified for model file in %s" % package, self.getId())
|
||||
model_file = os.path.split(mod.__file__)[0]
|
||||
else:
|
||||
if not os.path.isabs(model_file):
|
||||
raise ValueError(u"Model file name %s is not an absolute path and does not contain a package name in %s" % model_file, self.getId())
|
||||
|
||||
model = load_file(model_file, policy=u"dexterity")
|
||||
|
||||
return model
|
||||
|
||||
_fix_properties(DynamicFTI)
|
|
@ -0,0 +1,72 @@
|
|||
from zope.interface.declarations import Implements
|
||||
|
||||
from zope.component.interfaces import IFactory
|
||||
from zope.component import getUtility, queryUtility
|
||||
|
||||
from zope.app.component.hooks import getSiteManager
|
||||
|
||||
from zope.publisher.interfaces.browser import IBrowserView
|
||||
from zope.publisher.interfaces.browser import IBrowserRequest
|
||||
from zope.app.container.interfaces import IAdding
|
||||
|
||||
from plone.dexterity.interfaces import IDexterityFTI
|
||||
from plone.dexterity.factory import DexterityFactory
|
||||
from plone.dexterity.browser.add import AddViewFactory
|
||||
|
||||
from Products.CMFCore.interfaces import ISiteRoot
|
||||
|
||||
def register(fti):
|
||||
site = getUtility(ISiteRoot)
|
||||
site_manager = getSiteManager(site)
|
||||
|
||||
portal_type = fti.getId()
|
||||
|
||||
fti_utility = queryUtility(IDexterityFTI, name=portal_type)
|
||||
if fti_utility is None:
|
||||
site_manager.registerUtility(fti, IDexterityFTI, portal_type)
|
||||
|
||||
factory_utility = queryUtility(IFactory, name=fti.factory)
|
||||
if factory_utility is None:
|
||||
site_manager.registerUtility(DexterityFactory(portal_type), IFactory, fti.factory)
|
||||
|
||||
addview_factory = site_manager.adapters.lookup((Implements(IAdding), Implements(IBrowserRequest)), IBrowserView, name=fti.factory)
|
||||
if addview_factory is None:
|
||||
site_manager.registerAdapter(factory=AddViewFactory(portal_type),
|
||||
provided=IBrowserView,
|
||||
required=(IAdding, IBrowserRequest),
|
||||
name=fti.factory,)
|
||||
|
||||
def unregister(fti):
|
||||
site = queryUtility(ISiteRoot)
|
||||
if site is None:
|
||||
return
|
||||
|
||||
site_manager = getSiteManager(site)
|
||||
|
||||
portal_type = fti.getId()
|
||||
|
||||
site_manager.unregisterUtility(provided=IDexterityFTI, name=portal_type)
|
||||
site_manager.unregisterUtility(provided=IFactory, name=fti.factory)
|
||||
site_manager.unregisterAdapter(provided=IBrowserView, required=(IAdding, IBrowserRequest), name=fti.factory)
|
||||
|
||||
def fti_added(object, event):
|
||||
"""When the FTI is created, install local components
|
||||
"""
|
||||
|
||||
register(event.object)
|
||||
|
||||
def fti_removed(object, event):
|
||||
"""When the FTI is removed, uninstall local coponents
|
||||
"""
|
||||
|
||||
unregister(event.object.getId())
|
||||
|
||||
def fti_renamed(object, event):
|
||||
"""When the FTI is modified, ensure local components are still valid
|
||||
"""
|
||||
|
||||
if event.oldParent is None or event.newParent is None or event.oldName == event.newName:
|
||||
return
|
||||
|
||||
unregister(event.objec)
|
||||
register(event.object)
|
|
@ -0,0 +1,77 @@
|
|||
from zope.interface import Interface, alsoProvides
|
||||
from zope import schema
|
||||
|
||||
from zope.component.interfaces import IFactory
|
||||
from zope.app.content.interfaces import IContentType
|
||||
|
||||
class IDexterityFTI(Interface):
|
||||
"""The Factory Type Information for Dexterity content objects
|
||||
"""
|
||||
|
||||
def lookup_schema():
|
||||
"""Return an InterfaceClass that represents the schema of this type.
|
||||
Raises a ValueError if it cannot be found.
|
||||
"""
|
||||
|
||||
def lookup_model():
|
||||
"""Return the 'spec' dict for the model. See plone.supermodel for
|
||||
more information about the format of this.
|
||||
"""
|
||||
|
||||
behaviors = schema.List(title=u"Behaviors",
|
||||
description=u"A list of behaviors that are enabled for this type. "
|
||||
u"See plone.behavior for more details.",
|
||||
value_type=schema.DottedName(title=u"Behavior name"))
|
||||
|
||||
class IDynamicFTI(IDexterityFTI):
|
||||
"""Web/TTW-specific features of the FTI
|
||||
"""
|
||||
|
||||
model_file = schema.Text(title=u"Model file",
|
||||
description=u"A file that contains an XML model")
|
||||
|
||||
model_source = schema.Text(title=u"Model text",
|
||||
description=u"XML representation of the model for this type")
|
||||
|
||||
class IConcreteFTI(IDexterityFTI):
|
||||
"""Filesystem-specific features of the FTI
|
||||
"""
|
||||
|
||||
schema = schema.DottedName(title=u"Schema interface",
|
||||
description=u"Dotted name to an interface describing the type")
|
||||
|
||||
class IDexterityFactory(IFactory):
|
||||
"""A factory that can create Dexterity objects
|
||||
"""
|
||||
|
||||
portal_type = schema.TextLine(title=u"Portal type name",
|
||||
description=u"The portal type this is an FTI for")
|
||||
|
||||
# Schema
|
||||
|
||||
class IDexteritySchema(Interface):
|
||||
"""Base class for Dexterity schemata
|
||||
"""
|
||||
|
||||
alsoProvides(IDexteritySchema, IContentType)
|
||||
|
||||
class ITemporarySchema(Interface):
|
||||
"""Marker interface for partially constructed schemata
|
||||
"""
|
||||
class ITransientSchema(Interface):
|
||||
"""Marker interface for transient schemata
|
||||
"""
|
||||
|
||||
# Content
|
||||
|
||||
class IDexterityContent(Interface):
|
||||
"""Marker interface for dexterity-managed content objects
|
||||
"""
|
||||
|
||||
class IDexterityItem(IDexterityContent):
|
||||
"""Marker interface applied to dexterity-managed non-folderish objects
|
||||
"""
|
||||
|
||||
class IDexterityContainer(IDexterityContent):
|
||||
"""Marker interface applied to dexterity-managed folderish objects
|
||||
"""
|
|
@ -0,0 +1,68 @@
|
|||
import new
|
||||
|
||||
from zope.interface import implements, Interface, alsoProvides
|
||||
from zope.interface.interface import InterfaceClass
|
||||
|
||||
from plone.supermodel.parser import ISchemaPolicy
|
||||
|
||||
from plone.alterego.interfaces import IDynamicObjectFactory
|
||||
|
||||
from plone.dexterity.interfaces import IDexteritySchema
|
||||
|
||||
from plone.dexterity.interfaces import ITemporarySchema
|
||||
from plone.dexterity.interfaces import ITransientSchema
|
||||
from plone.dexterity import utils
|
||||
|
||||
from plone.alterego import dynamic
|
||||
|
||||
# Dynamic modules
|
||||
generated = dynamic.create('plone.dexterity.schema.generated')
|
||||
transient = new.module("transient")
|
||||
|
||||
class SchemaModuleFactory(object):
|
||||
"""Create dynamic schema interfaces on the fly
|
||||
"""
|
||||
|
||||
implements(IDynamicObjectFactory)
|
||||
|
||||
def __call__(self, name, module):
|
||||
"""Someone tried to load a dynamic interface that has not yet been
|
||||
created yet. We will attempt to load it from the FTI if we can. If
|
||||
the FTI doesn't exist, create a temporary marker interface that we
|
||||
can fill later.
|
||||
"""
|
||||
|
||||
bases = (Interface,)
|
||||
|
||||
try:
|
||||
prefix, portal_type, schema_name = utils.split_schema_name(name)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
is_default_schema = not schema_name
|
||||
if is_default_schema:
|
||||
bases += (IDexteritySchema,)
|
||||
|
||||
klass = InterfaceClass(name, bases, __module__=module.__name__)
|
||||
alsoProvides(klass, ITemporarySchema)
|
||||
return klass
|
||||
|
||||
class DexteritySchemaPolicy(object):
|
||||
"""Determines how and where imported dynamic interfaces are created.
|
||||
Note that these schemata are never used directly. Rather, they are merged
|
||||
into a schema with a proper name and module, either dynamically or
|
||||
in code.
|
||||
"""
|
||||
implements(ISchemaPolicy)
|
||||
|
||||
def module(self, schema_name, tree):
|
||||
return 'plone.dexterity.schema.transient'
|
||||
|
||||
def bases(self, schema_name, tree):
|
||||
return (ITransientSchema,)
|
||||
|
||||
def name(self, schema_name, tree):
|
||||
# We use a temporary name whilst the interface is being generated;
|
||||
# when it's first used, we know the portal_type and site, and can
|
||||
# thus update it
|
||||
return '__tmp__' + schema_name
|
|
@ -0,0 +1,13 @@
|
|||
import unittest
|
||||
|
||||
from plone.dexterity import utils
|
||||
|
||||
class TestConcreteFTI(unittest.TestCase):
|
||||
|
||||
def test_lookup_schema(self):
|
||||
self.fail()
|
||||
|
||||
def test_suite():
|
||||
suite = unittest.TestSuite()
|
||||
suite.addTest(unittest.makeSuite(TestConcreteFTI))
|
||||
return suite
|
|
@ -0,0 +1,25 @@
|
|||
import unittest
|
||||
|
||||
from plone.dexterity import utils
|
||||
|
||||
class TestDynamicFTI(unittest.TestCase):
|
||||
|
||||
def test_lookup_schema(self):
|
||||
self.fail()
|
||||
|
||||
def test_lookup_model_from_source(self):
|
||||
self.fail()
|
||||
|
||||
def test_lookup_model_from_file_with_package(self):
|
||||
self.fail()
|
||||
|
||||
def test_lookup_model_from_file_with_absolute_path(self):
|
||||
self.fail()
|
||||
|
||||
def test_lookup_model_from_file_with_win32_absolute_path(self):
|
||||
self.fail()
|
||||
|
||||
def test_suite():
|
||||
suite = unittest.TestSuite()
|
||||
suite.addTest(unittest.makeSuite(TestDynamicFTI))
|
||||
return suite
|
|
@ -0,0 +1,34 @@
|
|||
import unittest
|
||||
|
||||
from plone.dexterity import utils
|
||||
|
||||
class TestFactory(unittest.TestCase):
|
||||
|
||||
def test_title(self):
|
||||
self.fail()
|
||||
|
||||
def test_description(self):
|
||||
self.fail()
|
||||
|
||||
def test_create_basic(self):
|
||||
self.fail()
|
||||
|
||||
def test_create_sets_portal_type(self):
|
||||
self.fail()
|
||||
|
||||
def test_create_initialises_schema(self):
|
||||
self.fail()
|
||||
|
||||
def test_create_does_not_overwrite_attributes(self):
|
||||
self.fail()
|
||||
|
||||
def test_create_initializes_security(self):
|
||||
self.fail()
|
||||
|
||||
def test_get_interfaces(self):
|
||||
self.fail()
|
||||
|
||||
def test_suite():
|
||||
suite = unittest.TestSuite()
|
||||
suite.addTest(unittest.makeSuite(TestFactory))
|
||||
return suite
|
|
@ -0,0 +1,28 @@
|
|||
import unittest
|
||||
|
||||
from plone.dexterity import schema
|
||||
|
||||
class TestSchemaModuleFactory(unittest.TestCase):
|
||||
|
||||
def test_default_schema(self):
|
||||
self.fail()
|
||||
|
||||
def test_named_schema(self):
|
||||
self.fail()
|
||||
|
||||
def test_without_fti(self):
|
||||
self.fail()
|
||||
|
||||
class TestSchemaPolicy(unittest.TestCase):
|
||||
|
||||
def test_bases(self):
|
||||
self.fail()
|
||||
|
||||
def test_naming(self):
|
||||
self.fail()
|
||||
|
||||
def test_suite():
|
||||
suite = unittest.TestSuite()
|
||||
suite.addTest(unittest.makeSuite(TestSchemaModuleFactory))
|
||||
suite.addTest(unittest.makeSuite(TestSchemaPolicy))
|
||||
return suite
|
|
@ -0,0 +1,23 @@
|
|||
import unittest
|
||||
import mocker
|
||||
|
||||
from plone.dexterity import utils
|
||||
|
||||
class TestUtils(mocker.MockerTestCase):
|
||||
|
||||
def test_portal_type_to_schema_name(self):
|
||||
self.fail()
|
||||
|
||||
def test_schema_name_to_portal_type(self):
|
||||
self.fail()
|
||||
|
||||
def test_split_schema_name(self):
|
||||
self.fail()
|
||||
|
||||
def test_sync_schema(self):
|
||||
self.fail()
|
||||
|
||||
def test_suite():
|
||||
suite = unittest.TestSuite()
|
||||
suite.addTest(unittest.makeSuite(TestUtils))
|
||||
return suite
|
|
@ -0,0 +1,51 @@
|
|||
from zope.schema import getFieldsInOrder
|
||||
from zope.component import getUtility
|
||||
from Products.CMFCore.interfaces import ISiteRoot
|
||||
|
||||
# TODO: Need a better encoding algorithm here. The output needs to be
|
||||
# valid Python identifiers.
|
||||
|
||||
def encode(s):
|
||||
return s.replace(' ', '_1_').replace('.', '_2_')
|
||||
|
||||
def decode(s):
|
||||
return s.replace('_1_', ' ').replace('_2_', '.')
|
||||
|
||||
def join(*args):
|
||||
return '_0_'.join([encode(a) for a in args if a])
|
||||
|
||||
def split(s):
|
||||
return [decode(a) for a in s.split('_0_')]
|
||||
|
||||
def portal_type_to_schema_name(portal_type, schema=u"", prefix=None):
|
||||
"""Return a canonical interface name for a generated schema interface.
|
||||
"""
|
||||
if prefix is None:
|
||||
prefix = getUtility(ISiteRoot).getId()
|
||||
|
||||
return join(prefix, portal_type, schema)
|
||||
|
||||
def schema_name_to_portal_type(schema_name):
|
||||
"""Return a the portal_type part of a schema name
|
||||
"""
|
||||
return split(schema_name)[0]
|
||||
|
||||
def split_schema_name(schema_name):
|
||||
"""Return a tuple prefix, portal_type, schema_name
|
||||
"""
|
||||
items = split(schema_name)
|
||||
if len(items) == 2:
|
||||
return items[0], items[1], u""
|
||||
elif len(items) == 3:
|
||||
return items[0], items[1], items[2]
|
||||
else:
|
||||
raise ValueError("Schema name %s is invalid" % schema_name)
|
||||
|
||||
def sync_schema(source, dest, overwrite=False):
|
||||
"""Copy attributes from the source to the destination. If overwrite is
|
||||
False, do not overwrite attributes that already exist.
|
||||
"""
|
||||
|
||||
for name, field in getFieldsInOrder(source):
|
||||
if overwrite or not hasattr(dest, name):
|
||||
setattr(dest, name, field)
|
|
@ -0,0 +1,35 @@
|
|||
from setuptools import setup, find_packages
|
||||
import os
|
||||
|
||||
version = '1.0a1'
|
||||
|
||||
setup(name='plone.dexterity',
|
||||
version=version,
|
||||
description="CMF compatible integration of TTW-editable types",
|
||||
long_description=open("README.txt").read() + "\n" +
|
||||
open(os.path.join("docs", "HISTORY.txt")).read(),
|
||||
# Get more strings from http://www.python.org/pypi?%3Aaction=list_classifiers
|
||||
classifiers=[
|
||||
"Framework :: Plone",
|
||||
"Programming Language :: Python",
|
||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||
],
|
||||
keywords='',
|
||||
author='Martin Aspeli',
|
||||
author_email='optilude@gmail.com',
|
||||
url='http://plone.org',
|
||||
license='GPL',
|
||||
packages=find_packages(exclude=['ez_setup']),
|
||||
namespace_packages=['plone'],
|
||||
include_package_data=True,
|
||||
zip_safe=False,
|
||||
install_requires=[
|
||||
'setuptools',
|
||||
'plone.z3cform',
|
||||
'mocker',
|
||||
# -*- Extra requirements: -*-
|
||||
],
|
||||
entry_points="""
|
||||
# -*- Entry points: -*-
|
||||
""",
|
||||
)
|
Reference in New Issue