How can I change the URL of an object serverd by D

2019-09-02 07:37发布

问题:

I defined some content types based on Plone Dexterity, I want any content type has his own ID. So I used the zope.schema.Id .

class IArticle(form.Schema, IImageScaleTraversable):
    """
    Article
    """

    form.model("models/article.xml")

    id = schema.Id(
        title=_(u"Identifier"),
        description=_(u"The unique identifier of object."),
        required=False
    )

When I create a new article object, it works fine, but when I want to modify the Id of an existed article, It always show me error:

 {'args': (<MultiContentTreeWidget 'form.widgets.IRelatedItems.relatedItems'>,),
 'context': <Article at /dev/articles/this-is-another-articles>,
 'default': <object object at 0x100266b20>,
 'loop': {},
 'nothing': None,
 'options': {},
 'repeat': {},
 'request': <HTTPRequest, URL=http://localhost:8083/dev/articles/this-is-another-article/@@edit>,
 'template': <zope.browserpage.viewpagetemplatefile.ViewPageTemplateFile object at 0x10588ecd0>,
 'view': <MultiContentTreeWidget 'form.widgets.IRelatedItems.relatedItems'>,
 'views': <zope.browserpage.viewpagetemplatefile.ViewMapper object at 0x10b309f50>}
 .......
 .......
 .......
Module zope.tales.expressions, line 217, in __call__
Module zope.tales.expressions, line 211, in _eval
Module plone.formwidget.contenttree.widget, line 147, in render_tree
Module plone.app.layout.navigation.navtree, line 186, in buildFolderTree
Module Products.CMFPlone.CatalogTool, line 427, in searchResults
Module Products.ZCatalog.ZCatalog, line 604, in searchResults
Module Products.ZCatalog.Catalog, line 907, in searchResults
Module Products.ZCatalog.Catalog, line 656, in search
Module Products.ZCatalog.Catalog, line 676, in sortResults
Module plone.app.folder.nogopip, line 104, in documentToKeyMap
Module plone.folder.ordered, line 102, in getObjectPosition
Module plone.folder.default, line 130, in getObjectPosition
ValueError: No object with id "this-is-another-articles" exists.

回答1:

I don't think this is a use-case supported in the stock Edit form. You can rename items from the folder contents tab of the folder containing the item. Should you want to do this from the edit form:

  • You may need to use a custom edit form (subclass the existing form);
  • name your id field something other than 'id', perhaps 'target_id' or 'shortname'
  • have the __call__() method of the form update / save the form, and only on success rename the item within its container.
  • __call__() should redirect to the new item's URL after edit that triggers a name change.

Maybe something like (completely untested):

from plone.dexterity.browser.edit import DefaultEditForm as BaseForm
from Products.statusmessages.interfaces import IStatusMessages

class RenameEnabledEditForm(BaseForm):
    def __call__(self, *args, **kwargs):
         self.update(*args, **kwargs)
         data, errors = self.extractData()
         existing, newid = self.context.getId(), data.get('target_id')
         if not errors and existing != newid:
             parent_folder = self.context.__parent__
             if newid in parent_folder.objectIds():
                 raise RuntimeError('Duplicate object id')  # collision!
             parent_folder.manage_renameObject(existing, newid)
             newurl = parent_folder[newid].absolute_url()
             IStatusMessages(self.context).addStatusMessage(
                 u'Renamed item from "%s" to "%s" in folder' % (existing, newid),
                 type='info',
                 )
             self.request.response.redirect(newurl)
         else:
             return self.index(*args, **kwargs)


回答2:

I've achieved this with a custom add form so that only the add form includes id-field and the edit form doesn't (renaming is done in the old way through the rename-action or from the folder contents view).

Some boilerblate:

from five import grok

from zope import schema

from plone.directives import form
from plone.directives import dexterity


class IMyContent(form.Schema):
    """Schema of MyContent"""


class NotValidId(schema.ValidationError):
    """Id is not valid."""


def isValidId(value):
    """Validates id for new MyContent objects"""
    # raise NotValidId(value)
    return True


class IMyContentAddForm(IMyContent):
    """Schema of MyContent Add Form"""

    form.order_before(id="*")
    id = schema.ASCIILine(
        title=u"Id",
        constraint=isValidId,
        required=True
        )


class MyContentAddForm(dexterity.AddForm):
    """Add Form for new MyContent objects"""
    grok.name("my.app.mycontent")

    label = u"Add New"
    schema = IMyContentAddForm

    def create(self, data):
        """Creates a new object with the given (validated) id"""
        id = data.pop("id")
        obj = super(MyContentAddForm, self).create(data)
        obj.id = id
        return obj


回答3:

There's also an open issue and preliminary work for it to make Dexterity-types support the standard "Show 'Short Name' on content?"-feature.



回答4:

for use with dexterity content types, i adapted @spdupton's answer (correcting a typo in the import) and changing self.context to self.request

from Products.statusmessages.interfaces import IStatusMessage

class EditForm(dexterity.EditForm):
    grok.context(IPics)


    def applyChanges(self, data):    
        existing = self.context.getId()
        data, errors = self.extractData()
        super(EditForm, self).applyChanges(data)


        newid = str(data.get('picName'))

        print 'existing, newid =', existing, newid
        if not errors and existing != newid:
            parent_folder = self.context.__parent__
            if newid in parent_folder.objectIds():
                raise RuntimeError('Duplicate object id')  # collision!
            parent_folder.manage_renameObject(existing, newid)
            newurl = parent_folder[newid].absolute_url()
            IStatusMessage(self.request).addStatusMessage(
                u'Renamed item from "%s" to "%s"' % (existing, newid),
                type='info',
                )
            self.request.response.redirect(newurl)

#