Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions news/3953.breaking
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Move plone.app.dexterity views to plone.app.layout
[frapell]
7 changes: 7 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,23 @@
"plone.app.uuid",
"plone.app.viewletmanager >=1.2",
"plone.base >=4.0.0a1",
"plone.behavior",
"plone.dexterity",
"plone.formwidget.namedfile",
"plone.i18n",
"plone.memoize",
"plone.namedfile",
"plone.portlets",
"plone.protect",
"plone.registry",
"plone.schemaeditor",
"plone.supermodel",
"plone.z3cform",
"Products.CMFEditions >=1.2.2",
"Products.GenericSetup",
"Products.statusmessages",
"Products.ZCatalog",
"z3c.form",
"Zope",
],
extras_require=dict(
Expand Down
1 change: 1 addition & 0 deletions src/plone/app/layout/configure.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
directory="profiles/uninstall"
/>

<include package=".dexterity" />
<include package=".globals" />
<include package=".navigation" />
<include package=".viewlets" />
Expand Down
Empty file.
58 changes: 58 additions & 0 deletions src/plone/app/layout/dexterity/add_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from plone.app.dexterity import _
from plone.app.dexterity.interfaces import ITypeSettings
from plone.base.utils import safe_text
from plone.dexterity.fti import DexterityFTI
from plone.z3cform.layout import wrap_form
from Products.CMFCore.utils import getToolByName
from z3c.form import field
from z3c.form import form


class TypeAddForm(form.AddForm):
label = _("Add Content Type")
fields = field.Fields(ITypeSettings).select("title", "id", "description")
id = "add-type-form"
fti_id = None

def create(self, data):
id = data.pop("id")

fti = DexterityFTI(id)
fti.id = id
data["title"] = safe_text(data["title"])
if data["description"]:
data["description"] = safe_text(data["description"])
data["i18n_domain"] = "plone"
data["behaviors"] = "\n".join(
[
"plone.dublincore",
"plone.namefromtitle",
]
)
data["model_source"] = """
<model xmlns="http://namespaces.plone.org/supermodel/schema">
<schema>
</schema>
</model>
"""

data["klass"] = "plone.dexterity.content.Container"
data["filter_content_types"] = True
data["icon_expr"] = "string:file-earmark-text"
fti.manage_changeProperties(**data)
return fti

def add(self, fti):
ttool = getToolByName(self.context, "portal_types")
ttool._setObject(fti.id, fti)
self.fti_id = fti.id
self.status = _("Type added successfully.")

def nextURL(self):
url = self.context.absolute_url()
if self.fti_id is not None:
url += f"/{self.fti_id}/@@fields"
return url


TypeAddFormPage = wrap_form(TypeAddForm)
97 changes: 97 additions & 0 deletions src/plone/app/layout/dexterity/behaviors.pt
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<tal:root>
<div class="portalMessage"
tal:condition="view/status"
tal:content="view/status"
></div>

<table class="table">
<thead>
<tr>
<th class="w-25">
</th>
<th class="w-25">
<span i18n:translate="label_name_of_behavior">Name of behavior</span>
</th>
<th>
<span i18n:translate="label_interface_of_behavior">Interface of behavior</span>
</th>
</tr>
</thead>
</table>

<form action="."
method="post"
tal:attributes="
action request/getURL;
enctype view/enctype;
"
>
<tal:widgets repeat="widget view/widgets/values|nothing">
<tal:define tal:define="
behavior_name python: widget.field.__name__;
behavior_reg python: view.behaviors[behavior_name];
">
<div class="field"
tal:define="
error widget/error;
hidden python:widget.mode == 'hidden';
"
tal:attributes="
class python:'mb-3 field' + (error and 'alert alert-warning' or '');
"
>

<span class="fieldRequired"
title="Required"
tal:condition="python:widget.required and not hidden"
i18n:attributes="title title_required;"
i18n:translate="label_required"
>
(Required)
</span>

<div tal:condition="error"
tal:content="structure error/render"
>
Error
</div>

<div class="widget">
<table class="table table-borderless table-sm">
<tbody>
<tr>
<td class="w-25">
<input type="text"
tal:replace="structure widget/render"
/>
</td>
<td class="w-25"
tal:content="behavior_name"
>name of behavior</td>
<td tal:content="python:behavior_reg.interface.__identifier__">interface of behavior</td>
</tr>
<tr tal:define="
description widget/field/description;
"
tal:condition="python:description and not hidden"
>
<td colspan="3">
<span class="form-text"
tal:content="description"
i18n:translate=""
>field description
</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>

</tal:define>


</tal:widgets>
<metal:actions metal:use-macro="context/@@ploneform-macros/actions" />
</form>
</tal:root>
138 changes: 138 additions & 0 deletions src/plone/app/layout/dexterity/behaviors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
from collections import Counter
from copy import deepcopy
from operator import attrgetter
from plone.app.dexterity import _
from plone.app.layout.dexterity.layout import TypeFormLayout
from plone.app.dexterity.interfaces import ITypeSchemaContext
from plone.base.utils import safe_text
from plone.behavior.interfaces import IBehavior
from plone.behavior.registration import BehaviorRegistrationNotFound
from plone.behavior.registration import lookup_behavior_registration
from plone.dexterity.fti import DexterityFTIModificationDescription
from z3c.form import field
from z3c.form import form
from z3c.form.browser.checkbox import SingleCheckBoxFieldWidget
from zope import schema
from zope.browserpage.viewpagetemplatefile import ViewPageTemplateFile
from zope.component import adapter
from zope.component import getUtilitiesFor
from zope.lifecycleevent import modified

TTW_BEHAVIOR_BLACKLIST = [
# skip deprecated behavior
"plone.app.dexterity.behaviors.related.IRelatedItems",
]


def behaviorConfigurationModified(object, event):
description = DexterityFTIModificationDescription("behaviors", "")
modified(object.fti, description)


@adapter(ITypeSchemaContext)
class BehaviorConfigurationAdapter:
def __init__(self, context):
self.__dict__["context"] = context
self.__dict__["fti"] = self.context.fti

def __getattr__(self, name):
# be sure to get a valid value
reg = lookup_behavior_registration(name=name)
iid = reg.interface.__identifier__
return iid in self.fti.behaviors or safe_text(reg.name) in self.fti.behaviors

def __setattr__(self, name, value):
# add or remove the behavior based on the value from the form
behaviors = list(self.fti.behaviors)
reg = lookup_behavior_registration(name=name)
iid = reg.interface.__identifier__
if reg.name:
# behavior has a name -> use it
# but first remove the dotted behavior if present
if iid in self.fti.behaviors:
behaviors.remove(iid)
# prepare named behavior for add/remove
bname = safe_text(reg.name)
else:
# no name found -> prepare dotted behavior for add/remove instead
bname = iid

# add/remove bname if based on value True false
if value and bname not in behaviors:
behaviors.append(bname)
elif not value and bname in behaviors:
behaviors.remove(bname)

self.fti.behaviors = behaviors

def __iter__(self):
# iterate through the present behaviors
for name in self.fti.behaviors:
try:
reg = lookup_behavior_registration(name=name)
except BehaviorRegistrationNotFound:
# ignore wrong names
continue
if reg.name:
yield safe_text(reg.name)
else:
yield name


class TypeBehaviorsForm(form.EditForm):
template = ViewPageTemplateFile("behaviors.pt")
label = _("Behaviors")
description = _("Select the behaviors to enable for this content type.")
successMessage = _("Behaviors successfully updated.")
noChangesMessage = _("No changes were made.")
buttons = deepcopy(form.EditForm.buttons)
buttons["apply"].title = _("Save")

def getContent(self):
return BehaviorConfigurationAdapter(self.context)

@property
def behaviors(self):
"""Return dict of (behavior name, reg)"""
counts = Counter([id(reg) for name, reg in getUtilitiesFor(IBehavior)])
behavs = {}
for name, reg in getUtilitiesFor(IBehavior):
if name in TTW_BEHAVIOR_BLACKLIST:
# skip blacklisted
continue
with_name = counts[id(reg)] > 1
if with_name and reg.name != name:
continue
fname = safe_text(reg.name if reg.name else name)
behavs[fname] = reg
return behavs

@property
def fields(self):
counts = Counter([id(reg) for name, reg in getUtilitiesFor(IBehavior)])
fields = []
for name, reg in getUtilitiesFor(IBehavior):
if name in TTW_BEHAVIOR_BLACKLIST:
# skip blacklisted
continue
with_name = counts[id(reg)] > 1
if with_name and reg.name != name:
continue
fname = safe_text(reg.name if reg.name else name)
fields.append(
schema.Bool(
__name__=fname,
title=reg.title,
description=reg.description,
required=False,
)
)
form_fields = field.Fields(*sorted(fields, key=attrgetter("title")))
for ff in form_fields.values():
ff.widgetFactory = SingleCheckBoxFieldWidget
return form_fields


class TypeBehaviorsPage(TypeFormLayout):
form = TypeBehaviorsForm
label = _("Behaviors")
38 changes: 38 additions & 0 deletions src/plone/app/layout/dexterity/clone_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from plone.app.dexterity import _
from plone.app.dexterity.interfaces import ITypeSettings
from plone.dexterity.fti import DexterityFTI
from plone.z3cform.layout import wrap_form
from Products.CMFCore.utils import getToolByName
from z3c.form import field
from z3c.form import form


class TypeCloneForm(form.AddForm):
label = _("Clone Content Type")
fields = field.Fields(ITypeSettings).select("title", "id")
id = "clone-type-form"

def create(self, data):
type_id = data.pop("id")
props = dict(self.context.fti.propertyItems())
# make sure we don't share the factory
if props["factory"] == self.context.fti.getId():
del props["factory"]

props["title"] = data["title"]
props["add_view_expr"] = props["add_view_expr"].replace(
self.context.fti.getId(), type_id
)
fti = DexterityFTI(type_id, **props)
return fti

def add(self, fti):
ttool = getToolByName(self.context, "portal_types")
ttool._setObject(fti.id, fti)
self.status = _("Type cloned successfully.")

def nextURL(self):
return self.context.aq_parent.absolute_url()


TypeCloneFormPage = wrap_form(TypeCloneForm)
Loading
Loading