Skip to content
This repository has been archived by the owner on Apr 9, 2023. It is now read-only.

Commit

Permalink
Merge pull request #50 from plone/no-zcml-contenttypes
Browse files Browse the repository at this point in the history
be able to provide content types and behaviors with decorators
  • Loading branch information
bloodbare committed Jan 18, 2017
2 parents 93d63b5 + 8ab8b14 commit 7b2a6ee
Show file tree
Hide file tree
Showing 31 changed files with 647 additions and 207 deletions.
28 changes: 18 additions & 10 deletions docs/source/addons.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ Create an addon installer class in an `install.py` file in your `plone.server` a
```python

from plone.server.addons import Addon
from plone.server import configure

@configure.addon(
name="myaddon",
title="My addon")
class MyAddon(Addon):

@classmethod
Expand All @@ -26,19 +31,17 @@ class MyAddon(Addon):
pass
```

Then, in your `configure.zcml` file, register the addon::
**Scanning**
If your service modules are not imported at run-time, you may need to provide an
additional scan call to get your services noticed by `plone.server`.

```xml
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:plone="http://namespaces.plone.org/plone">
In your application `__init__.py` file, you can simply provide a `scan` call.

<include package="plone.server" file="meta.zcml" />
<plone:addon
name="myaddon"
title="My addon"
handler="pserver.myaddon.install.MyAddon" />
```python
from plone.server import configure

</configure>
def includeme(root):
configure.scan('my.package.addon')
```


Expand All @@ -51,7 +54,12 @@ from:

from plone.server.addons import Addon
from plone.server.registry import ILayers

LAYER = 'pserver.myaddon.interfaces.ILayer'

@configure.addon(
name="myaddon",
title="My addon")
class MyAddon(Addon):

@classmethod
Expand Down
46 changes: 46 additions & 0 deletions docs/source/applicationconfiguration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Application Configuration

`plone.server` handles configuration application customizations and extension
mostly with decorators in code.

This page is meant to be a reference to the available decorators and options
to those decorators.


## service

*`configure.service`*

* _context_: Content type interface this service is registered against. Example: ISite
* _method_: HTTP method this service works against. Default is `GET`
* _permission_: Permission this service requires. Default is configure default_permission setting
* _layer_: Layer this service is registered for. Default is `IDefaultLayer`
* _name_: This is used as part of the uri. Example `@foobar` -> `/mycontent/@foobar`. Leave empty to be used for base uri of content -> `/mycontent`.


## content type

*`configure.contenttype`*

* _portal_type_: Name of the content type
* _schema_: Interface schema to use for type
* _add_permission_: Permission required to add content. Defaults to `plone.AddContent`
* _allowed_types_: List of types allowed to be added inside this content assuming it is a Folder type. Defaults to allowing all types.


## behavior

*`configure.behavior`*

* _title_: Name of behavior
* _provides_: Interface this behavior provides
* _marker_: Marker interface to apply to utilized instance's behavior
* _for__: Content type this behavior is available for


## addon

*`configure.addon`*

* _name_
* _title_
20 changes: 14 additions & 6 deletions docs/source/applications.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

Applications are used to provide additional functionality to plone.server.

## Community Addons

Some useful addons to use in your own development:

- [pserver.elasticsearch](https://github.com/pyrenees/pserver.elasticsearch): Index content in elastic search
- [pserver.zodbusers](https://github.com/pyrenees/pserver.zodbusers): Store and authenticate users in the database
- [pserver.mailer](https://github.com/pyrenees/pserver.mailer): async send mail


## Creating

Expand Down Expand Up @@ -46,12 +54,6 @@ Once you create a `plone.server` application, there are three primary ways for i
to hook into `plone.server`.


### ZCML

If you're application is activated and has a `configure.zcml` file in it, it
will automatically be loaded.


### Call includeme function

Your application can provide an `includeme` function at the root of the module
Expand All @@ -69,3 +71,9 @@ def includeme(root):
If an `app_settings` dict is provided at the module root, it will automatically
merge the global `plone.server` app_settings with the module's. This allows you
to provide custom configuration.


### ZCML

If you're application is activated and has a `configure.zcml` file in it, it
will automatically be loaded.
51 changes: 30 additions & 21 deletions docs/source/behavior.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,14 @@ For example in case you want to have a class that stores the field on the conten
```python
from plone.server.behaviors.properties import ContextProperty
from plone.server.behaviors.intrance import AnnotationBehavior
from zope.component import adapter
from plone.server.interfaces import IResource
from plone.server import configure

@implementer(IMyLovedBehavior)
@adapter(IResource)
@configure.behavior(
title="Attachment",
provides=IMyLovedBehavior,
marker=IMarkerBehavior,
for_=IResource)
class MyBehavior(AnnotationBehavior):
"""If attributes
"""
Expand All @@ -55,33 +58,39 @@ class MyBehavior(AnnotationBehavior):

On this example text will be stored on the context object and text2 as a annotation.

Once you have the schema, marker interface and the factory you can register on zcml:

```xml
<plone:behavior
title="Attachment"
provides=".mybehavior.IMyLovedBehavior"
marker=".mybehavior.IMarkerBehavior"
factory=".mybehavior.MyBehavior"
for="plone.server.interfaces.IResource"
/>
## Static behaviors

With behaviors you can define them as static for specific content types:

```python

from plone.server import configure
from plone.server.interfaces import IItem
from plone.server.content import Item

@configure.contenttype(
portal_type="MyItem",
schema=IItem,
behaviors=["plone.server.behaviors.dublincore.IDublinCore"])
class MyItem(Item):
pass
```

**Scanning**
If your service modules are not imported at run-time, you may need to provide an
additional scan call to get your services noticed by `plone.server`.

## Static behaviors
In your application `__init__.py` file, you can simply provide a `scan` call.

With behaviors you can define them as static for specific content types:
```python
from plone.server import configure

```xml
<plone:contenttype
portal_type="Item"
schema=".content.IItem"
class=".content.Item"
behaviors=".behaviors.dublincore.IDublinCore"
/>
def includeme(root):
configure.scan('my.package.services')
```


### Create and modify content with behaviors

On the deserialization of the content you will need to pass on the POST/PATCH operation the behavior as a object on the JSON.
Expand Down
49 changes: 49 additions & 0 deletions docs/source/contenttypes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Content types

Content types allow you to provide custom schemas and content to your services.

OOTB, `plone.server` ships with simple `Folder` and `Item` content types. The
`Folder` type allowing someone to add items inside of it. Both types only have
simple dublin core fields.


## Defining content types

A content type consists of a class and optionally, a schema to define the custom
fields you want your class to use.

A simple type will look like this::

```python
from plone.server import configure
from plone.server.content import Folder
from plone.server.interfaces import IItem
from zope import schema

class IMySchema(IItem):
foo = schema.Text()

@configure.contenttype(
portal_type="MyType",
schema=IMySchema,
behaviors=["plone.server.behaviors.dublincore.IDublinCore"])
class MyType(Folder):
pass
```

This example creates a simple schema and assigns it to the `MyType` content
type.


**Scanning**
If your service modules are not imported at run-time, you may need to provide an
additional scan call to get your services noticed by `plone.server`.

In your application `__init__.py` file, you can simply provide a `scan` call.

```python
from plone.server import configure

def includeme(root):
configure.scan('my.package.content')
```
4 changes: 3 additions & 1 deletion docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ Contents:
applications
addons
services
contenttypes
behavior
interfaces
migrations
behavior
commands
applicationconfiguration
design

Indices and tables
Expand Down
26 changes: 20 additions & 6 deletions docs/source/services.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ creating API endpoints.
A service can be as simple as a function in your application:

```python
from plone.server.configure import service
from plone.server import configure
from plone.server.interfaces import ISite

@service(context=ISite, name='@myservice', method='GET',
permission='plone.AccessContent')
@configure.service(context=ISite, name='@myservice', method='GET',
permission='plone.AccessContent')
async def my_service(context, request):
return {
'foo': 'bar'
Expand All @@ -32,20 +32,34 @@ In this example, the service will apply to a GET request against a site,
`/zodb/plone/@myservice`.


**Scanning**
If your service modules are not imported at run-time, you may need to provide an
additional scan call to get your services noticed by `plone.server`.

In your application `__init__.py` file, you can simply provide a `scan` call.

```python
from plone.server import configure

def includeme(root):
configure.scan('my.package.services')
```


## class based services

For more complex services, you might want to use class based services.

The example above, with the class based approach will look like:

```python
from plone.server.configure import service
from plone.server import configure
from plone.server.interfaces import ISite
from plone.server.api.service import Service


@service(context=ISite, name='@myservice', method='GET',
permission='plone.AccessContent')
@configure.service(context=ISite, name='@myservice', method='GET',
permission='plone.AccessContent')
class DefaultGET(Service):
async def __call__(self):
# self.context
Expand Down
9 changes: 9 additions & 0 deletions src/plone.server/CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ Fixes:

New features:

- Be able to define addons using decorators, not zcml
[vangheem]

- Be able to define behaviors using decorators, not zcml
[vangheem]

- Be able to define content types using decorators, not zcml
[vangheem]

- Catalog reindex as async operation
[ramonnb]

Expand Down
14 changes: 7 additions & 7 deletions src/plone.server/plone/server/api/addons.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from plone.server import app_settings
from plone.server import configure
from plone.server.browser import ErrorResponse
from plone.server.configure import service
from plone.server.interfaces import ISite
from plone.server.registry import IAddons
from zope.i18nmessageid import MessageFactory
Expand All @@ -10,8 +10,8 @@
_ = MessageFactory('plone')


@service(context=ISite, name='@addons', method='POST',
permission='plone.ManageAddons')
@configure.service(context=ISite, name='@addons', method='POST',
permission='plone.ManageAddons')
async def install(context, request):
data = await request.json()
id_to_install = data.get('id', None)
Expand All @@ -33,8 +33,8 @@ async def install(context, request):
return await get_addons(context, request)()


@service(context=ISite, name='@addons', method='DELETE',
permission='plone.ManageAddons')
@configure.service(context=ISite, name='@addons', method='DELETE',
permission='plone.ManageAddons')
async def uninstall(context, request):
data = await request.json()
id_to_install = data.get('id', None)
Expand All @@ -56,8 +56,8 @@ async def uninstall(context, request):
config.enabled -= {id_to_install}


@service(context=ISite, name='@addons', method='GET',
permission='plone.ManageAddons')
@configure.service(context=ISite, name='@addons', method='GET',
permission='plone.ManageAddons')
async def get_addons(context, request):
result = {
'available': [],
Expand Down

0 comments on commit 7b2a6ee

Please sign in to comment.