Archetypes and Schemata() method - plone

I am customizing Plone Archetypes content type by overriding Schemata() method to have vocabulary contents dynamically:
def Schemata(self):
""" Overrides field definitions in fly.
"""
# XXX: Cache this method?
schemata = getSchemata(self).copy()
settings = self.getResearchSettings()
for row in settings.getFieldCustomizations():
name = row.get("fieldName", None)
vocab = row.get("vocabToUse", None)
field = schemata.get(name, None)
if field and vocab:
# Modify field copy ion
field.vocabulery = vocab
return schemata
Do I need to use cache decorator for Schemata() function or is Archetypes handling Schemata() internally so smart so that it calls it only once per request?
Plone 4.0.

forgot Schemata... you should not touch that stuff.
you can have dynamic vocabularies using object methods or zope vocabularies.
first hit on google

You definitely want to cache the results of Schema() and Schemata(), as at least Schema() is called multiple times during the same request.

Related

DjangoCMS: How to check for uniqueness of a field in a plugin model (CMSPlugin)?

I build a djangoCMS plugin which contain a slug. I want this slug to be unique.
Since djangoCMS store page content in draft and public versions (with a copy of plugins data), I can't do it at database level because it is normal two have at two plugins (draft and public versions) with the same slug.
I'm pretty sure there is an idiom for this but can't find it.
I've tried to make a check in the clean method of the model, but I can't access instance.placeholder which is None when being in clean method…
class MyPlugin(CMSPlugin):
slug = models.SlugField(
verbose_name=_("Slug"),
db_index=True,
max_length=255,
)
Any idea ?
I think I've found a solution by excluding objects where page is not draft:
def clean(self, *args, **kwargs):
sames_slug = MyPlugin.objects.filter(slug=self.slug).exclude(
placeholder__page__publisher_is_draft=False
)
if self.pk:
sames_slug = sames_slug.exclude(pk=self.pk)
if sames_slug.exists():
raise ValidationError(
{"slug": "There is already one with the same slug"}
)

Plone content allowed in specific folder

I've created a custom type dexterity, in Plone 4.3.9, called PersonalPage that must be only allowed in a specific folder Members_folder/userfolder .
In his FTI, by default <property name="global_allow">False</property>.
The userfolder is created in Members_folder with the uid of the authenticated user, in Plone 4.3.3:
if homefolder is None:
# create userfolder in members_folder
members_folder.invokeFactory('Folder', user_id)
userfolder = members_folder[user_id]
utils = userfolder.plone_utils
utils.changeOwnershipOf(userfolder, user_id,
1, None)
userfolder.setCreators([user_id])
roles = list(userfolder.get_local_roles_for_userid(user_id))
if 'Owner' not in roles:
roles.append('Owner')
userfolder.manage_setLocalRoles(user_id, roles)
if shasattr(userfolder, 'canSetConstrainTypes'):
userfolder.setConstrainTypesMode(1)
defaultAllowedTypes = userfolder.getLocallyAllowedTypes()
userfolder.setLocallyAllowedTypes(defaultAllowedTypes + ('personalpage',))
defaultAddableTypes = userfolder.getImmediatelyAddableTypes()
userfolder.setImmediatelyAddableTypes(defaultAddableTypes + ('personalpage',))
userfolder.reindexObjectSecurity()
userfolder.reindexObject()
But after running this script the content type PersonalPage doesn't appear in the list of the addable and allowed contents types of the userfolder.
What's wrong with this? Is there another way to allowed a content type addable only for a folder?
The script is not not working because of the global_allow flag.
Plone has 2 level of content type add restriction:
The first one is a global settings: if a content type is not globally allowed it can't be added anywhere until a content type explicitly list it as a possible subtype
The other one is the TTW addable types, that simply limits the set of contents defined above
If you need to limit addable types for a single folder you can define your folder as a new special type, used only once in the site (so keep global_allow true, create one folder, then disable it); finally specify PersonalPage as one of the allowed_types inside it and keep keep your global_allow settings for PersonalPage to False.
Alternatively you can use collective.factorymenu, to modify Plone add menu through the web (is not released yet but is working).

In Plone, how do I fix Archetypes content that is missing the cmf_uid() annotation?

I'm using Plone 4.3 with relstorage and I've somehow managed to lose the cmf_uid annotation on some of my content objects. This prevents collective.iterate from being able to check in content. Is there an easy way to have Plone walk through the database and re-add cmf_uid where it is missing? Already tried collective.catalogcleanup to no avail.
Here is a script that searches the portal (passed as context) for any Document that has a non-unique cmf_uid. Many of these documents actually have no cmf_uid but the indexed cmf_uid actually comes from the parent folder via Acquisition. Since the manifestation of the problem was that plone.app.iterate was unable to check in Document, the script adds a unique cmf_uid to just the Document types that appear to have non-unique cmf_uid but actually have no cmf_uid.
Although this adds cmf_uid to all Document, it would probably be sufficient to only add the attribute to documents currently being edited in checkouts.
"""
Add missing cmf_uid to Archetypes content.
cmf_uid is required to check in working copies of content.
"""
from Products.CMFUid.UniqueIdHandlerTool import UniqueIdError
from Acquisition import aq_inner
from collections import Counter
def add_missing_uids(context):
"""
context: the portal
"""
portal_uidhandler = context.portal_uidhandler
portal_uidgenerator = context.portal_uidgenerator
catalog = context.portal_catalog
brains = catalog.unrestrictedSearchResults()
freq = Counter(x.cmf_uid for x in brains)
for brain in brains:
# If it's only in use once then it's unique enough. Otherwise it's
# probably inheriting its indexed cmf_uid via Acquisition.
if freq[brain.cmf_uid] < 2 or brain.portal_type != 'Document':
continue
ob = aq_inner(brain.getObject())
if not portal_uidhandler.queryUid(ob):
print brain.Type, brain.portal_type, brain.getPath()
for i in range(3):
try:
portal_uidhandler.setUid(ob, portal_uidgenerator())
ob.reindexObject()
ob.reindexObject(idxs=['modified'])
break
except UniqueIdError:
print "RETRY"
else:
print "FAIL"

Plone 4.2 formwidget contenttree permissions

I have a dexterity content type in Plone 4.2.4, that uses the contenttree widget to manage referenced objects, but only in the editForm.
I realized, that the referenced items had to be external_visible to get displayed by the widget, which means that anonymous users could View and AccessContentsInformation. Thats not what I wanted. So I dug in the contenttree widget source and added the following to my products browser/configure.zcml
<include package="Products.CMFCore" file="permissions.zcml"
zcml:condition="installed plone.app.upgrade" />
<browser:page
for="*"
name="contenttree-fetch"
class="my.product.content.bikemetamodel.EditForm"
permission="cmf.ModifyPortalContent"
/>
<adapter factory="my.product.browser.widgets.MetamodellContenttreeAdapter" />
and an adapter
class MetamodellContenttreeAdapter(object):
implements(IBikeMetaModel)
adapts(Interface)
def __init__(self, context):
self.context = context
def _get_allowed_modeltypes(self):
return None
def _set_allowed_modeltypes(self, value):
print "setting", value
allowed_modeltypes = property(_get_allowed_modeltypes, _set_allowed_modeltypes)
[...]
But this seems to be not enough. The underlying catalog search returns no result, If the permissions are set to deny View and AccessContentsInformation to anonymous users. So I guess, I must use the view permission to construct some kind of proxy user.
Would it be ok, to use a SecurityManager in the newly created view to fetch the results as a different user? Or am I just missing something?
Ok, here is how I solved the mystery.
After digging around a while, I realized, that I missed the point with my previous idea to override the ##contenttree-fetch view. The solution I came up with is quite simple and seems elegant (enough) for me. I do a sudo style sidestep now, to gather the required items.
Class EditForm(dexterity.EditForm):
grok.context(IBikeMetaModel)
# If it would be another than the edit view, we could manage
# permisssions here. Not neccessary in edit view, because the
# edit permission defined in this content types *.xml counts
# grok.require("cmf.ModifyPortalContent")
#property
def acl_users(self):
return getToolByName(getSite(), 'acl_users')
def updateWidgets(self):
# This is the magic. A sudo style sidestep to a user
# with the id "system" that has permission to gather
# the required lists in the updateWidgets function of
# the base class
proxy_user = self.acl_users.getUserById("system")
oUser = getSecurityManager()
newSecurityManager(self.request, proxy_user)
super(EditForm, self).updateWidgets()
# custom widget updates
self.widgets['title'].mode = DISPLAY_MODE
self.widgets['year'].mode = HIDDEN_MODE
self.widgets['brand'].mode = HIDDEN_MODE
self.widgets['model'].mode = HIDDEN_MODE
# Very Important! Switch back to the original user.
setSecurityManager(oUser)

Why does my content object not show up in the portal_catalog?

I am trying to implement a basic Zope2 content type directly without using dexterity or Archetypes because I need this to be extremely lean.
from OFS.SimpleItem import SimpleItem
from Products.ZCatalog.CatalogPathAwareness import CatalogAware
from persistent.list import PersistentList
class Doculite(SimpleItem, CatalogAware):
""" implement our class """
meta_type = 'Doculite'
def __init__(self, id, title="No title", desc=''):
self.id = id
self.title = title
self.desc = desc
self.tags = PersistentList()
self.default_catalog = 'portal_catalog'
def add_tags(self, tags):
self.tags.extend(tags)
def Subject(self):
return self.tags
def indexObject(self):
self.reindex_object()
From an external method I am doing this:
def doit(self):
pc = self.portal_catalog
res1 = pc.searchResults()
o1 = self['doc1']
o1.add_tags(['test1', 'test2'])
o1.reindex_object()
res2 = pc.searchResults()
return 'Done'
I clear the catalog and run my external method. My object does not get into the catalog. But from the indexes tab, when I browse the Subject index, I can see my content item listed with the values. Both res1 and res2 and empty.
Why is my content item not showing up inside the searchResuts() of the catalog?
Plone is a full-fat content management system, if you're after something lean it's probably not the right choice (perhaps try Pyramid.)
For your content type to be a full part of a Plone site it has to fulfil a number of requirements across the Zope2, CMF and Plone layers. plone.app.content.item.Item is about the simplest base class you can get for a content item for a Plone site, though a simpler base class in itself will not really make instances of your content type any more 'lean' - an instance of a class in Python is basically just a dict and a pointer to it's class.
Most of the work on a page view will be rendering the various user interface features of a site. Rendering the schema based add/edit forms of frameworks like Archetypes and Dexterity is also relatively expensive.
I'd spend a little time profiling your application using one of the supported content type systems before putting time into building your own.
In order to see your objects in the "Catalog" tab of the portal_catalog your objects need to have a "getPhysicalPath()" method that returns a tuple representing their path (ex. ('','Plone','myobject')).
Also try to use this:
from Products.CMFCore.CMFCatalogAware import CMFCatalogAware
as base class.
You need to register your type with the catalog multiplexer. Look at the configuration in the zmi -> archetypes_tool.
I'm not sure, but you may also need a portal_type registration also...
Like Lawrence said though, you're better off just using one of the current content type frameworks if you want to be able to catalog your data with plone's portal catalog. If you can deal with a separate catalog, take a look at repoze.catalog.
Plone needs every content object to provide an "allowedRolesAndUsers" index to return the object in searchResults.
There is probably a zcml snippet that will enable this for my content type. But I was able to get things working by adding another method as follows:
def allowedRolesAndUsers(self):
return ['Manager', 'Authenticated', 'Anonymous']
CatalogAware will be removed in Zope 4 and then can't be used any more.
cf https://github.com/zopefoundation/Products.ZCatalog/issues/26

Resources