Change the id of an object in Plone when not published - plone

In our use case, if the title of an unpublished object is modified we would like to change its id according with the new title.
We try to accomplish it using a simple subscriber without success:
ZCML:
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:zcml="http://namespaces.zope.org/zcml">
<subscriber
for="DummyContentType
zope.lifecycleevent.IObjectModifiedEvent"
handler=".subscriber.update_id"
/>
</configure>
Python:
# -*- coding: utf-8 -*-
from plone import api
from plone.i18n.normalizer.interfaces import IIDNormalizer
from zope.component import queryUtility
def update_id(obj, event):
state = api.content.get_state(obj)
if state != 'private':
return
util = queryUtility(IIDNormalizer)
new_id = util.normalize(obj.Title())
api.content.rename(obj=obj, new_id=new_id, safe_id=True)
obj.reindexObject()
When this code run we get an ResourceLockedError exception

Related

Pact: Error when trying to setup mock provider

I'm trying to write my first Pact-python test using pytest, Could someone please tell me what's wrong with my code?
import unittest
import requests
import json
import pytest
import atexit
from pact import Consumer, Provider
pact = Consumer('Consumer').has_pact_with(Provider('Provider'), host_name='mockservice', port=8080)
pact.start_service()
atexit.register(pact.stop_service)
class InterviewDetails(unittest.TestCase):
def test_candidate_report_api(self):
candidate_report_payload = {}
resp = requests.post("http://localhost:1234/users/",data=json.dumps(candidate_report_payload))
response = json.loads(resp.text)
return response
#pytest.mark.health1
def test_candidate_report(self):
expected = {}
(pact.given('Comment')
.upon_receiving('comment')
.with_request(method='POST', path="http://localhost:1234/users/", headers={})
.will_respond_with(200, body=expected))
with pact:
pact.setup()
result = self.test_candidate_report_api()
self.assertEqual(result, expected)
pact.verify()
The error from stacktrace:
AttributeError: module 'pact' has no attribute 'Like'
Can you please confirm you're using pact-python from https://github.com/pact-foundation/pact-python/ (and not pactman, a project that is not maintained by the Pact Foundation)?
It might be related to the way you have setup your test?
Here is an example project you can use for reference: https://github.com/pactflow/example-consumer-python/
Relevant test code:
"""pact test for product service client"""
import json
import logging
import os
import requests
from requests.auth import HTTPBasicAuth
import pytest
from pact import Consumer, Like, Provider, Term, Format
from src.consumer import ProductConsumer
log = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO)
print(Format().__dict__)
PACT_MOCK_HOST = 'localhost'
PACT_MOCK_PORT = 1234
PACT_DIR = os.path.dirname(os.path.realpath(__file__))
#pytest.fixture
def consumer():
return ProductConsumer(
'http://{host}:{port}'
.format(host=PACT_MOCK_HOST, port=PACT_MOCK_PORT)
)
#pytest.fixture(scope='session')
def pact(request):
pact = Consumer('pactflow-example-consumer-python').has_pact_with(
Provider('pactflow-example-provider-python'), host_name=PACT_MOCK_HOST, port=PACT_MOCK_PORT,
pact_dir="./pacts", log_dir="./logs")
try:
print('start service')
pact.start_service()
yield pact
finally:
print('stop service')
pact.stop_service()
def test_get_product(pact, consumer):
expected = {
'id': "27",
'name': 'Margharita',
'type': 'Pizza'
}
(pact
.given('a product with ID 10 exists')
.upon_receiving('a request to get a product')
.with_request('GET', '/product/10')
.will_respond_with(200, body=Like(expected)))
with pact:
user = consumer.get_product('10')
assert user.name == 'Margharita'

Url Persian not found in django/passenger

sample Url:
site.com/category/%D9%81%D8%AA%D9%88%DA%AF%D8%B1%D8%A7%D9%81%DB%8C_%D8%B1%DB%8C%D9%86%D9%88%D9%BE%D9%84%D8%A7%D8%B3%D8%AA%DB%8C/
url config:
url(r'^category/(?P<page_slug>.*)/$', views.category, name='category'),
passenger config:
import imp
import os
import sys
sys.path.insert(0, os.path.dirname(__file__))
wsgi = imp.load_source('wsgi', 'photography/wsgi.py')
application = wsgi.application
wsgi config:
"""
WSGI config for photography project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "photography.settings")
#os.environ["DJANGO_SETTINGS_MODULE"] = "photography.settings"
application = get_wsgi_application()
but response 404 not found in url!
in problem for all url persian slug.
when change config wsgi to unquote:
from urllib.parse import unquote
def application(environ, start_fn):
environ['PATH_INFO'] = unquote(environ['PATH_INFO'])
app = get_wsgi_application()
print(environ)
return app(environ, start_fn)
change url into:
site.com/category/%C3%99%C2%81%C3%98%C2%AA%C3%99%C2%88%C3%9A%C2%AF%C3%98%C2%B1%C3%98%C2%A7%C3%99/tag/%D9%81%D8%AA%D9%88%DA%AF%D8%B1%D8%A7%D9%81%DB%8C-%D9%BE%D8%B2%D8%B4%DA%A9%DB%8C/
But there is an open problem !
I applied all the changes I found with the search, but there is still a problem!
Output one of the changes in the wsgi:
App 3585081 output: set_script_prefix(get_script_name(environ))
App 3585081 output: File "/home/sepandteb/virtualenv/sepandteb/3.5/lib/python3.5/site-packages/django/core/handlers/wsgi.py", line 210, in get_script_name
App 3585081 output: return script_name.decode(UTF_8)
App 3585081 output: UnicodeDecodeError: 'utf-8' codec can't decode byte 0xc3 in position 27: unexpected end of data
use encode('utf-8') for Persian character and set # -*- coding: utf-8 -*- on top of file.
for example when you save data on DB or read on DB use this function.
i.e :
slug.encode('utf-8')
You can use "uri_to_iri" in the view, for example:
from django.shortcuts import get_object_or_404
from django.utils.encoding import uri_to_iri
def blog_detail(request, slug):
post= get_object_or_404(Post, slug=uri_to_iri(slug))

Scrapy: one jobdir per spider

I have a Scrapy project with multiple spiders. How can I run all spiders with its own jobdir? I show in the following code how am I executing all spiders with a single jobdir.
from scrapy.crawler import CrawlerProcess
from scrapy.utils.project import get_project_settings
settings = get_project_settings()
settings.set('JOBDIR', 'saved_crawl', priority='cmdline')
process = CrawlerProcess(settings)
process.crawl('spider1')
process.crawl('spider2')
process.start()
Ok, I found that the solution is very simple. I just need to define JOBDIR in the custom settings dict, for each spider:
class Spider1(scrapy.Spider):
name = 'spider1'
custom_settings = {'JOBDIR': 'crawl_spider1'}

My Archetypes-based content type can't be added

I'm developing an add-on package which introduces a few Archetypes-based content types;
these are defined in the default profile of that package.
After (re-) installing my package in the Quick-Installer, I can see my types in the types tool; but I can't add them TTW, and they are not listed in the folder_constraintypes_form. I did select them in the "Allowed content types" multiselect list of the Folder portal type.
Since I got a ValueError from FactoryTypeInformation._getFactoryMethod in an bin/instance debug session, I "developed" Products.CMFPlone (branch 2.2) and changed the TypesTool.py like so:
from pprint import pprint # ADDED
...
class FactoryTypeInformation(TypeInformation):
...
def _getFactoryMethod(self, container, check_security=1):
if not self.product or not self.factory:
raise ValueError, ('Product factory for %s was undefined' %
self.getId())
pd = container.manage_addProduct # ADDED
p = container.manage_addProduct[self.product]
self_product = self.product # ADDED
self_factory = self.factory # ADDED
m = getattr(p, self.factory, None)
if m is None:
pprint(locals()) # ADDED
raise ValueError, ('Product factory for %s was invalid' %
self.getId())
if not check_security:
return m
if getSecurityManager().validate(p, p, self.factory, m):
return m
raise AccessControl_Unauthorized( 'Cannot create %s' % self.getId() )
The debug session now looks like this:
>>> root = app.plone
>>> from Products.CMFCore.utils import getToolByName
>>> tmp_folder = root.temp
>>> type_name = 'MyType'
>>> types_tool = getToolByName(tmp_folder, 'portal_types')
>>> type_info = types_tool.getTypeInfo(type_name)
>>> type_info
<DynamicViewTypeInformation at /plone/portal_types/MyType>
>>> new_content_item = type_info._constructInstance(tmp_folder, 'shiny_new_object')
{'check_security': 0,
'container': <ATFolder at /plone/temp>,
'pd': <App.FactoryDispatcher.ProductDispatcher object at 0x227afd0>,
'p': <App.FactoryDispatcher.FactoryDispatcher object at 0x7b97450>,
'self': <DynamicViewTypeInformation at /plone/portal_types/MyType>,
'm': None,
'self_factory': 'addMyType',
'self_product': 'MyCompany.MyProduct'}
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "/opt/zope/instances/zope-devel/src/Products.CMFCore/Products/CMFCore/TypesTool.py", line 551, in _constructInstance
m = self._getFactoryMethod(container, check_security=0)
File "/opt/zope/instances/zope-devel/src/Products.CMFCore/Products/CMFCore/TypesTool.py", line 467, in _getFactoryMethod
self.getId())
ValueError: Product factory for MyType was invalid
So, the FactoryDispatcher lacks the necessary addMyType attribute.
Probably my declarations are incomplete?
This is what I have:
config.py:
# -*- coding: utf-8 -*-
from Products.CMFCore.permissions import setDefaultRoles
from os import sep
from .permissions import (AddMyType,
)
PROJECTNAME = "MyCompany.MyProduct"
PRODUCT_HOME = sep.join(__file__.split(sep)[:-1])
MANAGERS_ONLY = ('Manager',)
MANAGERS_AND_OWNER = ('Manager', 'Owner')
# Permissions
DEFAULT_ADD_CONTENT_PERMISSION = "Add portal content"
setDefaultRoles(DEFAULT_ADD_CONTENT_PERMISSION, MANAGERS_AND_OWNER)
ADD_CONTENT_PERMISSIONS = {
'MyType': AddMyType,
}
for perm in ADD_CONTENT_PERMISSIONS.values():
setDefaultRoles(perm, MANAGERS_ONLY)
content/mytype.py:
# -*- coding: utf-8 -*-
__author__ = """unknown <unknown>"""
__docformat__ = 'plaintext'
from AccessControl import ClassSecurityInfo
from zope.interface import implements
from ..interfaces import IMyType
from ..config import PROJECTNAME
from Products.ATContentTypes.content.base import ATCTContent
from Products.ATContentTypes.content.schemata import ATContentTypeSchema
from Products.ATContentTypes.content.base import registerATCT as registerType
MyType_schema = (
ATContentTypeSchema.copy()
)
class MyType(ATCTContent):
"""
description of my type
"""
security = ClassSecurityInfo()
implements(IMyType)
meta_type = 'MyType'
_at_rename_after_creation = True
schema = MyType_schema
registerType(MyType, PROJECTNAME)
interfaces.py:
# -*- coding: utf-8 -*-
"""Module where all interfaces, events and exceptions live."""
from zope.publisher.interfaces.browser import IDefaultBrowserLayer
from zope.interface import Interface
class ISupBetonqualiLayer(IDefaultBrowserLayer):
"""Marker interface that defines a browser layer."""
class IMyType(Interface):
"""Marker interface for .mytype.MyType
"""
permissions.py:
# -*- coding: utf-8 -*- vim: ts=8 sts=4 sw=4 si et tw=79
"""
Permissions
"""
AddMyType = 'MyCompany.MyProduct: Add MyType'
profiles/default/factorytool.xml:
<?xml version="1.0"?>
<object name="portal_factory" meta_type="Plone Factory Tool">
<factorytypes>
<type portal_type="MyType"/>
</factorytypes>
</object>
profiles/default/rolemap.xml:
<?xml version="1.0"?>
<rolemap>
<roles>
<role name="MyAuthor"/>
</roles>
<permissions>
<permission name="MyCompany.MyProduct: Add MyType" acquire="True">
<role name="MyAuthor"/>
<role name="Manager"/>
</permission>
</permissions>
</rolemap>
profiles/default/types.xml:
<?xml version="1.0"?>
<object name="portal_types"
meta_type="Plone Types Tool">
<object name="MyType"
meta_type="Factory-based Type Information with dynamic views"/>
</object>
profiles/default/types/MyType.xml:
<?xml version="1.0"?>
<object name="MyType"
meta_type="Factory-based Type Information with dynamic views"
xmlns:i18n="http://xml.zope.org/namespaces/i18n">
<property name="title">MyType</property>
<property name="description">
Some description text which is indeed visible in the types tool
</property>
<property name="content_icon">SomeExisting.png</property>
<property name="content_meta_type">MyType</property>
<property name="product">MyCompany.MyProduct</property>
<property name="factory">addMyType</property>
<property name="immediate_view">mytype_view</property>
<property name="global_allow">True</property>
<property name="filter_content_types">False</property>
<property name="allowed_content_types">
</property>
<property name="allow_discussion">False</property>
<property name="default_view">mytype_view</property>
<property name="view_methods">
<element value="base_view"/>
</property>
<property name="default_view_fallback">False</property>
<alias from="(Default)" to="(dynamic view)"/>
<alias from="index.html" to="(dynamic view)"/>
<alias from="view" to="(selected layout)"/>
<alias from="edit" to="base_edit"/>
<alias from="properties" to="base_metadata"/>
<action title="View"
action_id="view"
category="object"
condition_expr=""
url_expr="string:${object_url}/view"
visible="True">
<permission value="View"/>
</action>
<action title="Edit"
action_id="edit"
category="object"
condition_expr="not:object/##plone_lock_info/is_locked_for_current_user"
url_expr="string:${object_url}/edit"
visible="True">
<permission value="Modify portal content"/>
</action>
</object>
Should not Archetypes take care of creating that missing addMyType method?
What could make this fail?
Is there something obviously missing in my configuration?
The site contains Archtypes-based objects exclusively so far. Will I come into trouble if I add Dexterity-based types now? ''(I'm totally inexperienced with Dexterity)''
Before someone tells me to do so: I created a question in the Plone community forum already; no luck so far. If important information comes in on either page, I'll sync it.
These are the missing parts to make your contenttype addable:
1.) Register the content-directory in MyCompany/MyProduct/configure.zcml by adding:
<include package=".content" />
2.) Add the file MyCompany/MyProduct/content/configure.zcml with this content:
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:five="http://namespaces.zope.org/five"
i18n_domain="MyCompany.MyProduct">
<class class=".mytype.MyType">
<require
permission="zope2.View"
interface="..interfaces.IMyType"
/>
</class>
</configure>
3.) Fix the then occurring syntax-error in MyCompany/MyProduct/content/mytype.py by replacing class MyType(*basecls) with class MyType(ATCTContent).
And last but not least remove the former attempts of making things work. Best would be to outsource the type to a dedicated pckg and create it with zopeskel, imo.
For the view-error occurring after adding a type, feel free to open a new quest ;-)

Create a portal_user_catalog and have it used (Plone)

I'm creating a fork of my Plone site (which has not been forked for a long time). This site has a special catalog object for user profiles (a special Archetypes-based object type) which is called portal_user_catalog:
$ bin/instance debug
>>> portal = app.Plone
>>> print [d for d in portal.objectMap() if d['meta_type'] == 'Plone Catalog Tool']
[{'meta_type': 'Plone Catalog Tool', 'id': 'portal_catalog'},
{'meta_type': 'Plone Catalog Tool', 'id': 'portal_user_catalog'}]
This looks reasonable because the user profiles don't have most of the indexes of the "normal" objects, but have a small set of own indexes.
Since I found no way how to create this object from scratch, I exported it from the old site (as portal_user_catalog.zexp) and imported it in the new site. This seemed to work, but I can't add objects to the imported catalog, not even by explicitly calling the catalog_object method. Instead, the user profiles are added to the standard portal_catalog.
Now I found a module in my product which seems to serve the purpose (Products/myproduct/exportimport/catalog.py):
"""Catalog tool setup handlers.
$Id: catalog.py 77004 2007-06-24 08:57:54Z yuppie $
"""
from Products.GenericSetup.utils import exportObjects
from Products.GenericSetup.utils import importObjects
from Products.CMFCore.utils import getToolByName
from zope.component import queryMultiAdapter
from Products.GenericSetup.interfaces import IBody
def importCatalogTool(context):
"""Import catalog tool.
"""
site = context.getSite()
obj = getToolByName(site, 'portal_user_catalog')
parent_path=''
if obj and not obj():
importer = queryMultiAdapter((obj, context), IBody)
path = '%s%s' % (parent_path, obj.getId().replace(' ', '_'))
__traceback_info__ = path
print [importer]
if importer:
print importer.name
if importer.name:
path = '%s%s' % (parent_path, 'usercatalog')
print path
filename = '%s%s' % (path, importer.suffix)
print filename
body = context.readDataFile(filename)
if body is not None:
importer.filename = filename # for error reporting
importer.body = body
if getattr(obj, 'objectValues', False):
for sub in obj.objectValues():
importObjects(sub, path+'/', context)
def exportCatalogTool(context):
"""Export catalog tool.
"""
site = context.getSite()
obj = getToolByName(site, 'portal_user_catalog', None)
if tool is None:
logger = context.getLogger('catalog')
logger.info('Nothing to export.')
return
parent_path=''
exporter = queryMultiAdapter((obj, context), IBody)
path = '%s%s' % (parent_path, obj.getId().replace(' ', '_'))
if exporter:
if exporter.name:
path = '%s%s' % (parent_path, 'usercatalog')
filename = '%s%s' % (path, exporter.suffix)
body = exporter.body
if body is not None:
context.writeDataFile(filename, body, exporter.mime_type)
if getattr(obj, 'objectValues', False):
for sub in obj.objectValues():
exportObjects(sub, path+'/', context)
I tried to use it, but I have no idea how it is supposed to be done;
I can't call it TTW (should I try to publish the methods?!).
I tried it in a debug session:
$ bin/instance debug
>>> portal = app.Plone
>>> from Products.myproduct.exportimport.catalog import exportCatalogTool
>>> exportCatalogTool(portal)
Traceback (most recent call last):
File "<console>", line 1, in <module>
File ".../Products/myproduct/exportimport/catalog.py", line 58, in exportCatalogTool
site = context.getSite()
AttributeError: getSite
So, if this is the way to go, it looks like I need a "real" context.
Update: To get this context, I tried an External Method:
# -*- coding: utf-8 -*-
from Products.myproduct.exportimport.catalog import exportCatalogTool
from pdb import set_trace
def p(dt, dd):
print '%-16s%s' % (dt+':', dd)
def main(self):
"""
Export the portal_user_catalog
"""
g = globals()
print '#' * 79
for a in ('__package__', '__module__'):
if a in g:
p(a, g[a])
p('self', self)
set_trace()
exportCatalogTool(self)
However, wenn I called it, I got the same <PloneSite at /Plone> object as the argument to the main function, which didn't have the getSite attribute. Perhaps my site doesn't call such External Methods correctly?
Or would I need to mention this module somehow in my configure.zcml, but how? I searched my directory tree (especially below Products/myproduct/profiles) for exportimport, the module name, and several other strings, but I couldn't find anything; perhaps there has been an integration once but was broken ...
So how do I make this portal_user_catalog work?
Thank you!
Update: Another debug session suggests the source of the problem to be some transaction matter:
>>> portal = app.Plone
>>> puc = portal.portal_user_catalog
>>> puc._catalog()
[]
>>> profiles_folder = portal.some_folder_with_profiles
>>> for o in profiles_folder.objectValues():
... puc.catalog_object(o)
...
>>> puc._catalog()
[<Products.ZCatalog.Catalog.mybrains object at 0x69ff8d8>, ...]
This population of the portal_user_catalog doesn't persist; after termination of the debug session and starting fg, the brains are gone.
It looks like the problem was indeed related with transactions.
I had
import transaction
...
class Browser(BrowserView):
...
def processNewUser(self):
....
transaction.commit()
before, but apparently this was not good enough (and/or perhaps not done correctly).
Now I start the transaction explicitly with transaction.begin(), save intermediate results with transaction.savepoint(), abort the transaction explicitly with transaction.abort() in case of errors (try / except), and have exactly one transaction.commit() at the end, in the case of success. Everything seems to work.
Of course, Plone still doesn't take this non-standard catalog into account; when I "clear and rebuild" it, it is empty afterwards. But for my application it works well enough.

Resources