SlideShare a Scribd company logo
1 of 62
Download to read offline
Dexterity in the Wild
Technical case study of a complex Dexterity-based integration
David Glick
 • web developer at Groundwire Consulting

 • Plone core developer

 • Dexterity maintainer
• Strategy and technology consulting for mission-driven organizations and
    businesses

  • Building relationships to create change that helps build thriving communities
    and a healthy planet.

Services:

  • engagement strategy

  • websites (Plone)

  • CRM databases (Salesforce.com)
• Net Impact's mission is to mobilize a new generation to use their careers to
  drive transformational change in their workplaces and the world.

• 501(c)3 based in San Francisco

• over 280 chapters worldwide
Process
1. Strategy

2. Technical discovery

3. Implementation (CRM and web)
Goals
 • Build on top of proven, extensible platforms

 • Reorganize and simplify their extensive content

 • Provide an enhanced and streamlined experience for members
Key features
 • Browsable member directory & editable member profiles

 • Member data managed in Salesforce but presented on the website

 • Conference registration

 • Chapter directory

 • Webinar archive

Coming:

 • Chapter leader portal

 • Member Mail

 • Job board
Implementation notes
Member database
Requirement: Members are searchable and get their own profile page (and can
be easily synced with Salesforce without using
collective.salesforce.authplugin).

Solution: Members as content.
Membrane
  • Allows Plone users to be represented as content items

  • Provides PluggableAuthService plugins which look up the user item in a
    special catalog (the membrane_tool), then adapt to IMembraneObject to get
    an implementation suitable for accomplishing a particular task.

Plugins for:

  • Authentication

  • User properties

  • etc.
dexterity.membrane
dexterity.membrane
 • Behavior to turn a content type into a member.

 • Takes care of:

     ◦ Name content item based on person's first/last name.

     ◦ Authentication

     ◦ Provide fullname and bio properties to Plone

     ◦ Allow the user to edit their profile

     ◦ Password resets

 • Only requirement is your content type must have these fields:

     ◦ first_name, last_name, homepage, bio, password
Membrane: the ugly
 • extra catalog with unneeded indexes
The member profile workflow
Requirement: Users can choose whether or not their profiles are public.




Solution: A boolean in the member schema, plus an auto-triggering workflow.
Auto-triggering workflow
Two states:

  • membersonly

  • private

Plus an initial state, "autotrigger".

Plus two automatic transitions out of the autotrigger state.
Automatic workflow transitions
 • Fires after any manual workflow transition.

 • Doesn't show up in the workflow menu.

Example from the workflow definition:

<transition transition_id="auto_to_private" new_state="private"
            title="Members only"
            trigger="AUTOMATIC"
            before_script="" after_script="">
                                            >
  <guard>
    <guard-expression>
    <guard-expression>not:object/@@netimpact-utils/is_contact_publishable</guard-expres
                                                                         </guard-expres
  </guard>
</transition>
The workflow transition trigger
We need a manual transition to make the automatic magic happen!

@grok.subscribe(IContact, IObjectModifiedEvent)
def trigger_contact_workflow(contact, event):
    wtool = getToolByName(contact, 'portal_workflow')
    wtool.doActionFor(contact, 'autotrigger')
The result




Overkill? Maybe.
Multi-level workflow
Requirement: Any content can be designated as public, private, or visible to two
levels of member (free & paid).

Specific instance: The member directory is only accessible to members.

Solution: custom default workflow.
The two_level_member_workflow
Most content can be assigned one of these states:


  • Private - visible to Net Impact staff only

  • Premium - visible to paid members only

  • Members-only - visible to members and supporting (paid)
    members

  • Public - visible to anyone
Roles
These levels of access are modeled using 3 built-in roles:

  • Site Administrator (for staff)

  • Member (for free members)

  • Anonymous (for the public)

And one custom role:

  • Paid Member
Granting the correct roles based on member status
Membrane lets us assign custom roles using an IMembraneUserRoles adapter:

class ContactRoleProvider
      ContactRoleProvider(grok.Adapter, MembraneUser):
    grok.context(IContact)
    grok.implements(IMembraneUserRoles)

    def __init__(self, context):
        self.context = context

    def getRolesForPrincipal(self, principal, request=None):
        roles = []
        if self.context.is_staff:
            roles.append('Site Administrator')
        roles.append('Member')
        if self.context.member_status in ('Premium', 'Lifetime'):
            roles.append('Paid Member')
        return roles
Registration and profile editing
Requirement: Multi-part profile editing form with overlays.

Solution: Lots of z3c.form forms based on the content model.
XML model
In part:

<model xmlns="http://namespaces.plone.org/supermodel/schema"
       xmlns:form="http://namespaces.plone.org/supermodel/form">>
  <schema>
    <fieldset name="links" label="Links">>
      <field name="homepage" type="zope.schema.ASCIILine"
              form:validator="netimpact.content.validators.URLValidator">
                                                                        >
         <title>
         <title>Personal Website</title>
                                </title>
         <description>
         <description>Include http://</description>
                                     </description>
         <required>
         <required>False</required>
                        </required>
      </field>
      <field name="twitter" type="zope.schema.TextLine"
              form:omitted="true">
                                 >
         <title>
         <title>Twitter</title>
                       </title>
         <description>
         <description>Enter your twitter id (e.g. netimpact)</description>
                                                            </description>
         <required>
         <required>False</required>
                        </required>
      </field>
    </fieldset>
  </schema>
</model>
Connecting the model to a concrete schema
We want to use a schema called IContact, not whatever Dexterity generates for
us.

In interfaces.py:

from zope.interface import alsoProvides
from plone.directives import form
from zope.app.content.interfaces import IContentType

class IContact
      IContact(form.Schema):
    form.model('models/contact.xml')
alsoProvides(IContact, IContentType)

In profiles/default/types/netimpact.contact.xml:

<property name="schema">netimpact.content.interfaces.IContact</property>
                       >                                     </property>
Using that schema to build a form
Unusual requirements:

 • We have multiple forms with different fields, so can't use autoform.

 • Late binding of the model means we have to defer form field setup.

from plone.directives import dexterity
from netimpact.content.interfaces import IContact

class EditProfileNetworking
      EditProfileNetworking(dexterity.EditForm):
    grok.name('edit-networking')
    label = u'Networking'

    # avoid autoform functionality
    def updateFields(self):
        pass

    @property
    def fields(self):
        return field.Fields(IContact).select('homepage', 'company_homepage',
                                             'twitter', 'linkedin')
Data grid (collective.z3cform.datagridfield)
Autocomplete
Chapter selection
Searching the member directory
Requirement: Members get access to a member directory searchable by keyword,
chapter, location, job function, issue, industry, or sector.

Solution: eea.facetednavigation
Custom listings for members
Requirement: Members show in listings with custom info (school or company and
location).




Solution:

  • Override folder_listing

  • Make search results use folder_listing
Synchronizing content with Salesforce.com
Requirement: Manage and report on members in Salesforce, present the
directory on the web.

Solution: Nightly data sync.
collective.salesforce.content




http://github.com/Groundwire/collective.salesforce.content
Contact schema with Salesforce metadata
<model xmlns="http://namespaces.plone.org/supermodel/schema"
       xmlns:form="http://namespaces.plone.org/supermodel/form"
       xmlns:sf="http://namespaces.plone.org/salesforce/schema">>
  <schema sf:object="Contact"
          sf:container="/member-directory"
          sf:criteria="Member_Status__c != null">
                                                >

    <field name="email" type="zope.schema.ASCIILine"
      form:validator="netimpact.content.validators.EmailValidator"
      security:read-permission="cmf.ModifyPortalContent"
      sf:field="Email">
                      >
      <title>
      <title>E-mail Address</title>
                           </title>

    </field>
  </schema>
</model>

Performs a query like:

SELECT Id, Email FROM Contact WHERE Member_Status__c != null
Extending Dexterity schemas
Parameterized behavior.

 • Storage: Schema tagged values

 • In Python schemas: new grok directives

 • In XML model: new XML directives in custom namespace

 • TTW: Custom views to edit the tagged values
Field with custom value converter
We wanted to convert Salesforce Ids of Chapters into the Plone UUID of
corresponding Chapter items:

<field name="chapter" type="zope.schema.Choice"
       form:widget="netimpact.content.browser.widgets.ChapterFieldWidget"
       sf:field="Chapter__c" sf:converter="uuid">
                                                >
  <title>
  <title>Chapter</title>
                 </title>
  <description></description>
  <vocabulary>
  <vocabulary>netimpact.content.Chapters</vocabulary>
                                        </vocabulary>
  <required>
  <required>True</required>
                 </required>
  <default>
  <default>n/a</default>
               </default>
</field>
Custom value converters
The converter:

from collective.salesforce.behavior.converters import DefaultValueConverter

class UUIDConverter
      UUIDConverter(DefaultValueConverter, grok.Adapter):
    grok.provides(ISalesforceValueConverter)
    grok.context(IField)
    grok.name('uuid')

    def toSchemaValue(self, value):
        if value:
            res = get_catalog().searchResults(sf_object_id=value)
            if res:
                return res[0].UID
Handling collections of related info
Education list of dicts in main Contact schema:

<field name="education" type="zope.schema.List"
       form:widget="collective.z3cform.datagridfield.DataGridFieldFactory"
       sf:relationship="Schools_Attended__r">
                                            >
  <title>
  <title>Most Recent School</title>
                            </title>
  <required>
  <required>True</required>
                </required>
  <min_length> </min_length>
  <min_length>1</min_length>
  <value_type type="collective.z3cform.datagridfield.DictRow">
                                                             >
    <schema>
    <schema>netimpact.content.interfaces.IEducationInfo</schema>
                                                       </schema>
  </value_type>
</field>
The subschema
IEducationInfo is another model-based schema:

from plone.directives import form

class IEducationInfo
      IEducationInfo(form.Schema):
    form.model('models/education_info.xml')

<model xmlns="http://namespaces.plone.org/supermodel/schema"
       xmlns:form="http://namespaces.plone.org/supermodel/form"
       xmlns:sf="http://namespaces.plone.org/salesforce/schema">>
  <schema sf:object="School_Attended__c"
          sf:criteria="Organization__c != ''
                       ORDER BY Graduation_Date__c asc NULLS LAST">
                                                                  >
    <field name="school_id" type="zope.schema.TextLine" sf:field="Organization__c">
                                                                                  >
      <title>
      <title>School ID</title>
                      </title>
      <required>
      <required>False</required>
                     </required>
    </field>
  </schema>
</model>

SELECT Id, (SELECT Organization__c FROM School_Attended__c) FROM Contact
            SELECT
Writing back to Salesforce
Handled less automatically, in response to an ObjectModifiedEvent:

@grok.subscribe(IContact, IObjectModifiedEvent)
def save_contact_to_salesforce(contact, event):
    if not IModifiedViaSalesforceSync.providedBy(event):
        upsertMember(contact)
Handling payments
Requirement: Accept payments for:

 • Several types of membership

 • Conference registration

 • Conference expo exhibitors

 • Chapter dues

Solution: groundwire.checkout
groundwire.checkout
Pieces of GetPaid groundwire.checkout reuses
 • Core objects (cart and order storage)

 • Payment processing code (Authorize.net)

 • Compatible with getpaid.formgen and pfg.donationform
What groundwire.checkout provides

 • Single-page z3c.form-based checkout form with:

     ◦ cart listing,

     ◦ credit card info fieldset

     ◦ billing address fieldset

     ◦ much, much easier to customize than PloneGetPaid's

 • Order confirmation view with summary of the completed transaction

 • Agnostic as to how items get added to the cart; only handles checkout

 • API for performing actions after an item is purchased
Basic example
Add an item to the cart and redirect to checkout:

from getpaid.core.item import PayableLineItem
from groundwire.checkout.utils import get_cart
from groundwire.checkout.utils import redirect_to_checkout

item = PayableLineItem()
item.item_id = 'item'
item.name = 'My Item'
item.cost = float(5)
item.quantity = 1

cart = get_cart()
if 'item' in cart:
    del cart['item']
cart['item'] = item
redirect_to_checkout()
Performing actions after purchase
Custom item classes can perform their own actions:

from getpaid.core.item import PayableLineItem

class MyLineItem
      MyLineItem(PayableLineItem):

    def after_charged(self):
        print 'charged!'
Pricing
Products are managed in Salesforce.




But we need to determine the constituency (and thus the price) in Plone.
Product content type
Discounts




 • Auto-apply vs. coded discounts
Mixed theming approach
 • Diazo without a theme

<theme if-content="false()" href="theme.html" />

<!-- Add the site slogan after the logo (example rule with XSLT) -->
<replace css:content="#portal-logo">
                                   >
    <xsl:copy-of css:select="#portal-logo" />
    <p id="portal-slogan">Where good works.</p>
                         >                 </p>
</replace>

 • z3c.jbot to make changes to templates
Edit bar at top




<replace css:content="#visual-portal-wrapper">
                                             >
    <xsl:copy-of css:select="#edit-bar" />
    <div id="visual-portal-wrapper">>
        <xsl:apply-templates />
    </div>
</replace>
<replace css:content="#edit-bar" />
Tile-based layout




<div class="tile-placeholder"
     tal:attributes="data-tile-href string:${portal_url}/
         @@groundwire.tiles.richtext/login-newmember-features" />
Conclusion
What Plone could do
 • Rewrite the password reset tool

 • Better support for multiple levels of membership

 • Easier way to customize a type's listing view

 • Asynchronous processing infrastructure

 • Built-in support for tiles
What Dexterity could do
 • Make it possible to parameterize widgets and validators in the model

 • Better way to make multiple forms based on the same schema

 • Expand the through-the-web editor
What Plone gives for free (or cheap)
Plone was absolutely the right tool for the job.

  • Basic content management

  • Custom form creation using PloneFormGen

  • Fine-grained access control

  • Collections

  • Basic content types
Visit the site
http://netimpact.org
Contact me
David Glick
     dglick@groundwireconsulting.com

Groundwire Consulting
     http://groundwireconsulting.com
Questions?

More Related Content

What's hot

Django Architecture Introduction
Django Architecture IntroductionDjango Architecture Introduction
Django Architecture IntroductionHaiqi Chen
 
The Django Web Application Framework 2
The Django Web Application Framework 2The Django Web Application Framework 2
The Django Web Application Framework 2fishwarter
 
Building a Dynamic Website Using Django
Building a Dynamic Website Using DjangoBuilding a Dynamic Website Using Django
Building a Dynamic Website Using DjangoNathan Eror
 
Django Rest Framework and React and Redux, Oh My!
Django Rest Framework and React and Redux, Oh My!Django Rest Framework and React and Redux, Oh My!
Django Rest Framework and React and Redux, Oh My!Eric Palakovich Carr
 
Working with the django admin
Working with the django admin Working with the django admin
Working with the django admin flywindy
 
Angular를 활용한 웹 프론트단 개발과 2.0에서 달라진점
Angular를 활용한 웹 프론트단 개발과 2.0에서 달라진점Angular를 활용한 웹 프론트단 개발과 2.0에서 달라진점
Angular를 활용한 웹 프론트단 개발과 2.0에서 달라진점Jeado Ko
 
web2py:Web development like a boss
web2py:Web development like a bossweb2py:Web development like a boss
web2py:Web development like a bossFrancisco Ribeiro
 
Django Framework and Application Structure
Django Framework and Application StructureDjango Framework and Application Structure
Django Framework and Application StructureSEONGTAEK OH
 
Best Practices in Plugin Development (WordCamp Seattle)
Best Practices in Plugin Development (WordCamp Seattle)Best Practices in Plugin Development (WordCamp Seattle)
Best Practices in Plugin Development (WordCamp Seattle)andrewnacin
 
Python & Django TTT
Python & Django TTTPython & Django TTT
Python & Django TTTkevinvw
 
Web development with django - Basics Presentation
Web development with django - Basics PresentationWeb development with django - Basics Presentation
Web development with django - Basics PresentationShrinath Shenoy
 
Writing your Third Plugin
Writing your Third PluginWriting your Third Plugin
Writing your Third PluginJustin Ryan
 
Introduction To Django (Strange Loop 2011)
Introduction To Django (Strange Loop 2011)Introduction To Django (Strange Loop 2011)
Introduction To Django (Strange Loop 2011)Jacob Kaplan-Moss
 
Rails vs Web2py
Rails vs Web2pyRails vs Web2py
Rails vs Web2pyjonromero
 
Expression Language in JSP
Expression Language in JSPExpression Language in JSP
Expression Language in JSPcorneliuskoo
 
Django app deployment in Azure By Saurabh Agarwal
Django app deployment in Azure By Saurabh AgarwalDjango app deployment in Azure By Saurabh Agarwal
Django app deployment in Azure By Saurabh Agarwalratneshsinghparihar
 
Behaviour Driven Development con Behat & Drupal
Behaviour Driven Development con Behat & DrupalBehaviour Driven Development con Behat & Drupal
Behaviour Driven Development con Behat & Drupalsparkfabrik
 
Building Pluggable Web Applications using Django
Building Pluggable Web Applications using DjangoBuilding Pluggable Web Applications using Django
Building Pluggable Web Applications using DjangoLakshman Prasad
 
Becoming A Drupal Master Builder
Becoming A Drupal Master BuilderBecoming A Drupal Master Builder
Becoming A Drupal Master BuilderPhilip Norton
 
The Django Web Application Framework
The Django Web Application FrameworkThe Django Web Application Framework
The Django Web Application FrameworkSimon Willison
 

What's hot (20)

Django Architecture Introduction
Django Architecture IntroductionDjango Architecture Introduction
Django Architecture Introduction
 
The Django Web Application Framework 2
The Django Web Application Framework 2The Django Web Application Framework 2
The Django Web Application Framework 2
 
Building a Dynamic Website Using Django
Building a Dynamic Website Using DjangoBuilding a Dynamic Website Using Django
Building a Dynamic Website Using Django
 
Django Rest Framework and React and Redux, Oh My!
Django Rest Framework and React and Redux, Oh My!Django Rest Framework and React and Redux, Oh My!
Django Rest Framework and React and Redux, Oh My!
 
Working with the django admin
Working with the django admin Working with the django admin
Working with the django admin
 
Angular를 활용한 웹 프론트단 개발과 2.0에서 달라진점
Angular를 활용한 웹 프론트단 개발과 2.0에서 달라진점Angular를 활용한 웹 프론트단 개발과 2.0에서 달라진점
Angular를 활용한 웹 프론트단 개발과 2.0에서 달라진점
 
web2py:Web development like a boss
web2py:Web development like a bossweb2py:Web development like a boss
web2py:Web development like a boss
 
Django Framework and Application Structure
Django Framework and Application StructureDjango Framework and Application Structure
Django Framework and Application Structure
 
Best Practices in Plugin Development (WordCamp Seattle)
Best Practices in Plugin Development (WordCamp Seattle)Best Practices in Plugin Development (WordCamp Seattle)
Best Practices in Plugin Development (WordCamp Seattle)
 
Python & Django TTT
Python & Django TTTPython & Django TTT
Python & Django TTT
 
Web development with django - Basics Presentation
Web development with django - Basics PresentationWeb development with django - Basics Presentation
Web development with django - Basics Presentation
 
Writing your Third Plugin
Writing your Third PluginWriting your Third Plugin
Writing your Third Plugin
 
Introduction To Django (Strange Loop 2011)
Introduction To Django (Strange Loop 2011)Introduction To Django (Strange Loop 2011)
Introduction To Django (Strange Loop 2011)
 
Rails vs Web2py
Rails vs Web2pyRails vs Web2py
Rails vs Web2py
 
Expression Language in JSP
Expression Language in JSPExpression Language in JSP
Expression Language in JSP
 
Django app deployment in Azure By Saurabh Agarwal
Django app deployment in Azure By Saurabh AgarwalDjango app deployment in Azure By Saurabh Agarwal
Django app deployment in Azure By Saurabh Agarwal
 
Behaviour Driven Development con Behat & Drupal
Behaviour Driven Development con Behat & DrupalBehaviour Driven Development con Behat & Drupal
Behaviour Driven Development con Behat & Drupal
 
Building Pluggable Web Applications using Django
Building Pluggable Web Applications using DjangoBuilding Pluggable Web Applications using Django
Building Pluggable Web Applications using Django
 
Becoming A Drupal Master Builder
Becoming A Drupal Master BuilderBecoming A Drupal Master Builder
Becoming A Drupal Master Builder
 
The Django Web Application Framework
The Django Web Application FrameworkThe Django Web Application Framework
The Django Web Application Framework
 

Similar to Dexterity in the Wild

Introduction Django
Introduction DjangoIntroduction Django
Introduction DjangoWade Austin
 
Iasi code camp 12 october 2013 shadow dom - mihai bîrsan
Iasi code camp 12 october 2013   shadow dom - mihai bîrsanIasi code camp 12 october 2013   shadow dom - mihai bîrsan
Iasi code camp 12 october 2013 shadow dom - mihai bîrsanCodecamp Romania
 
Web Components v1
Web Components v1Web Components v1
Web Components v1Mike Wilcox
 
Get things done with Yii - quickly build webapplications
Get things done with Yii - quickly build webapplicationsGet things done with Yii - quickly build webapplications
Get things done with Yii - quickly build webapplicationsGiuliano Iacobelli
 
Angular - Chapter 4 - Data and Event Handling
 Angular - Chapter 4 - Data and Event Handling Angular - Chapter 4 - Data and Event Handling
Angular - Chapter 4 - Data and Event HandlingWebStackAcademy
 
Cis407 a ilab 6 web application development devry university
Cis407 a ilab 6 web application development devry universityCis407 a ilab 6 web application development devry university
Cis407 a ilab 6 web application development devry universitylhkslkdh89009
 
"Umbraco MVC - a journey of discovery" - Lotte Pitcher
"Umbraco MVC - a journey of discovery" - Lotte Pitcher"Umbraco MVC - a journey of discovery" - Lotte Pitcher
"Umbraco MVC - a journey of discovery" - Lotte Pitcherlottepitcher
 
Modul-Entwicklung für Magento, OXID eShop und Shopware (2013)
Modul-Entwicklung für Magento, OXID eShop und Shopware (2013)Modul-Entwicklung für Magento, OXID eShop und Shopware (2013)
Modul-Entwicklung für Magento, OXID eShop und Shopware (2013)Roman Zenner
 
Staying Sane with Drupal NEPHP
Staying Sane with Drupal NEPHPStaying Sane with Drupal NEPHP
Staying Sane with Drupal NEPHPOscar Merida
 
How to Write Custom Modules for PHP-based E-Commerce Systems (2011)
How to Write Custom Modules for PHP-based E-Commerce Systems (2011)How to Write Custom Modules for PHP-based E-Commerce Systems (2011)
How to Write Custom Modules for PHP-based E-Commerce Systems (2011)Roman Zenner
 
The Django Web Application Framework 2
The Django Web Application Framework 2The Django Web Application Framework 2
The Django Web Application Framework 2fishwarter
 
The Django Web Application Framework 2
The Django Web Application Framework 2The Django Web Application Framework 2
The Django Web Application Framework 2fishwarter
 
PHPConf-TW 2012 # Twig
PHPConf-TW 2012 # TwigPHPConf-TW 2012 # Twig
PHPConf-TW 2012 # TwigWake Liu
 
The Django Web Application Framework 2
The Django Web Application Framework 2The Django Web Application Framework 2
The Django Web Application Framework 2fishwarter
 
3-TIER ARCHITECTURE IN ASP.NET MVC
3-TIER ARCHITECTURE IN ASP.NET MVC3-TIER ARCHITECTURE IN ASP.NET MVC
3-TIER ARCHITECTURE IN ASP.NET MVCMohd Manzoor Ahmed
 
Creating Single Page Web App using Backbone JS
Creating Single Page Web App using Backbone JSCreating Single Page Web App using Backbone JS
Creating Single Page Web App using Backbone JSAkshay Mathur
 

Similar to Dexterity in the Wild (20)

Introduction Django
Introduction DjangoIntroduction Django
Introduction Django
 
KAAccessControl
KAAccessControlKAAccessControl
KAAccessControl
 
Iasi code camp 12 october 2013 shadow dom - mihai bîrsan
Iasi code camp 12 october 2013   shadow dom - mihai bîrsanIasi code camp 12 october 2013   shadow dom - mihai bîrsan
Iasi code camp 12 october 2013 shadow dom - mihai bîrsan
 
Web Components v1
Web Components v1Web Components v1
Web Components v1
 
Get things done with Yii - quickly build webapplications
Get things done with Yii - quickly build webapplicationsGet things done with Yii - quickly build webapplications
Get things done with Yii - quickly build webapplications
 
Angular - Chapter 4 - Data and Event Handling
 Angular - Chapter 4 - Data and Event Handling Angular - Chapter 4 - Data and Event Handling
Angular - Chapter 4 - Data and Event Handling
 
Customizing User Profiles
Customizing User ProfilesCustomizing User Profiles
Customizing User Profiles
 
Cis407 a ilab 6 web application development devry university
Cis407 a ilab 6 web application development devry universityCis407 a ilab 6 web application development devry university
Cis407 a ilab 6 web application development devry university
 
Discovering Django - zekeLabs
Discovering Django - zekeLabsDiscovering Django - zekeLabs
Discovering Django - zekeLabs
 
"Umbraco MVC - a journey of discovery" - Lotte Pitcher
"Umbraco MVC - a journey of discovery" - Lotte Pitcher"Umbraco MVC - a journey of discovery" - Lotte Pitcher
"Umbraco MVC - a journey of discovery" - Lotte Pitcher
 
Modul-Entwicklung für Magento, OXID eShop und Shopware (2013)
Modul-Entwicklung für Magento, OXID eShop und Shopware (2013)Modul-Entwicklung für Magento, OXID eShop und Shopware (2013)
Modul-Entwicklung für Magento, OXID eShop und Shopware (2013)
 
WOdka
WOdkaWOdka
WOdka
 
Staying Sane with Drupal NEPHP
Staying Sane with Drupal NEPHPStaying Sane with Drupal NEPHP
Staying Sane with Drupal NEPHP
 
How to Write Custom Modules for PHP-based E-Commerce Systems (2011)
How to Write Custom Modules for PHP-based E-Commerce Systems (2011)How to Write Custom Modules for PHP-based E-Commerce Systems (2011)
How to Write Custom Modules for PHP-based E-Commerce Systems (2011)
 
The Django Web Application Framework 2
The Django Web Application Framework 2The Django Web Application Framework 2
The Django Web Application Framework 2
 
The Django Web Application Framework 2
The Django Web Application Framework 2The Django Web Application Framework 2
The Django Web Application Framework 2
 
PHPConf-TW 2012 # Twig
PHPConf-TW 2012 # TwigPHPConf-TW 2012 # Twig
PHPConf-TW 2012 # Twig
 
The Django Web Application Framework 2
The Django Web Application Framework 2The Django Web Application Framework 2
The Django Web Application Framework 2
 
3-TIER ARCHITECTURE IN ASP.NET MVC
3-TIER ARCHITECTURE IN ASP.NET MVC3-TIER ARCHITECTURE IN ASP.NET MVC
3-TIER ARCHITECTURE IN ASP.NET MVC
 
Creating Single Page Web App using Backbone JS
Creating Single Page Web App using Backbone JSCreating Single Page Web App using Backbone JS
Creating Single Page Web App using Backbone JS
 

Recently uploaded

CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):comworks
 
Key Features Of Token Development (1).pptx
Key  Features Of Token  Development (1).pptxKey  Features Of Token  Development (1).pptx
Key Features Of Token Development (1).pptxLBM Solutions
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking MenDelhi Call girls
 
08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking MenDelhi Call girls
 
Breaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountBreaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountPuma Security, LLC
 
Human Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsHuman Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsMark Billinghurst
 
Benefits Of Flutter Compared To Other Frameworks
Benefits Of Flutter Compared To Other FrameworksBenefits Of Flutter Compared To Other Frameworks
Benefits Of Flutter Compared To Other FrameworksSoftradix Technologies
 
Hyderabad Call Girls Khairatabad ✨ 7001305949 ✨ Cheap Price Your Budget
Hyderabad Call Girls Khairatabad ✨ 7001305949 ✨ Cheap Price Your BudgetHyderabad Call Girls Khairatabad ✨ 7001305949 ✨ Cheap Price Your Budget
Hyderabad Call Girls Khairatabad ✨ 7001305949 ✨ Cheap Price Your BudgetEnjoy Anytime
 
Slack Application Development 101 Slides
Slack Application Development 101 SlidesSlack Application Development 101 Slides
Slack Application Development 101 Slidespraypatel2
 
Presentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreterPresentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreternaman860154
 
Pigging Solutions in Pet Food Manufacturing
Pigging Solutions in Pet Food ManufacturingPigging Solutions in Pet Food Manufacturing
Pigging Solutions in Pet Food ManufacturingPigging Solutions
 
The Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxThe Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxMalak Abu Hammad
 
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024BookNet Canada
 
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...shyamraj55
 
IAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsIAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsEnterprise Knowledge
 
Injustice - Developers Among Us (SciFiDevCon 2024)
Injustice - Developers Among Us (SciFiDevCon 2024)Injustice - Developers Among Us (SciFiDevCon 2024)
Injustice - Developers Among Us (SciFiDevCon 2024)Allon Mureinik
 
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024BookNet Canada
 
SIEMENS: RAPUNZEL – A Tale About Knowledge Graph
SIEMENS: RAPUNZEL – A Tale About Knowledge GraphSIEMENS: RAPUNZEL – A Tale About Knowledge Graph
SIEMENS: RAPUNZEL – A Tale About Knowledge GraphNeo4j
 
Azure Monitor & Application Insight to monitor Infrastructure & Application
Azure Monitor & Application Insight to monitor Infrastructure & ApplicationAzure Monitor & Application Insight to monitor Infrastructure & Application
Azure Monitor & Application Insight to monitor Infrastructure & ApplicationAndikSusilo4
 

Recently uploaded (20)

CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):
 
Key Features Of Token Development (1).pptx
Key  Features Of Token  Development (1).pptxKey  Features Of Token  Development (1).pptx
Key Features Of Token Development (1).pptx
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men
 
08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men
 
Breaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountBreaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path Mount
 
Human Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsHuman Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR Systems
 
Benefits Of Flutter Compared To Other Frameworks
Benefits Of Flutter Compared To Other FrameworksBenefits Of Flutter Compared To Other Frameworks
Benefits Of Flutter Compared To Other Frameworks
 
Hyderabad Call Girls Khairatabad ✨ 7001305949 ✨ Cheap Price Your Budget
Hyderabad Call Girls Khairatabad ✨ 7001305949 ✨ Cheap Price Your BudgetHyderabad Call Girls Khairatabad ✨ 7001305949 ✨ Cheap Price Your Budget
Hyderabad Call Girls Khairatabad ✨ 7001305949 ✨ Cheap Price Your Budget
 
Slack Application Development 101 Slides
Slack Application Development 101 SlidesSlack Application Development 101 Slides
Slack Application Development 101 Slides
 
Presentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreterPresentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreter
 
Pigging Solutions in Pet Food Manufacturing
Pigging Solutions in Pet Food ManufacturingPigging Solutions in Pet Food Manufacturing
Pigging Solutions in Pet Food Manufacturing
 
The Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxThe Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptx
 
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
 
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
 
IAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsIAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI Solutions
 
Vulnerability_Management_GRC_by Sohang Sengupta.pptx
Vulnerability_Management_GRC_by Sohang Sengupta.pptxVulnerability_Management_GRC_by Sohang Sengupta.pptx
Vulnerability_Management_GRC_by Sohang Sengupta.pptx
 
Injustice - Developers Among Us (SciFiDevCon 2024)
Injustice - Developers Among Us (SciFiDevCon 2024)Injustice - Developers Among Us (SciFiDevCon 2024)
Injustice - Developers Among Us (SciFiDevCon 2024)
 
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
 
SIEMENS: RAPUNZEL – A Tale About Knowledge Graph
SIEMENS: RAPUNZEL – A Tale About Knowledge GraphSIEMENS: RAPUNZEL – A Tale About Knowledge Graph
SIEMENS: RAPUNZEL – A Tale About Knowledge Graph
 
Azure Monitor & Application Insight to monitor Infrastructure & Application
Azure Monitor & Application Insight to monitor Infrastructure & ApplicationAzure Monitor & Application Insight to monitor Infrastructure & Application
Azure Monitor & Application Insight to monitor Infrastructure & Application
 

Dexterity in the Wild

  • 1. Dexterity in the Wild Technical case study of a complex Dexterity-based integration
  • 2. David Glick • web developer at Groundwire Consulting • Plone core developer • Dexterity maintainer
  • 3. • Strategy and technology consulting for mission-driven organizations and businesses • Building relationships to create change that helps build thriving communities and a healthy planet. Services: • engagement strategy • websites (Plone) • CRM databases (Salesforce.com)
  • 4. • Net Impact's mission is to mobilize a new generation to use their careers to drive transformational change in their workplaces and the world. • 501(c)3 based in San Francisco • over 280 chapters worldwide
  • 5. Process 1. Strategy 2. Technical discovery 3. Implementation (CRM and web)
  • 6. Goals • Build on top of proven, extensible platforms • Reorganize and simplify their extensive content • Provide an enhanced and streamlined experience for members
  • 7. Key features • Browsable member directory & editable member profiles • Member data managed in Salesforce but presented on the website • Conference registration • Chapter directory • Webinar archive Coming: • Chapter leader portal • Member Mail • Job board
  • 9. Member database Requirement: Members are searchable and get their own profile page (and can be easily synced with Salesforce without using collective.salesforce.authplugin). Solution: Members as content.
  • 10. Membrane • Allows Plone users to be represented as content items • Provides PluggableAuthService plugins which look up the user item in a special catalog (the membrane_tool), then adapt to IMembraneObject to get an implementation suitable for accomplishing a particular task. Plugins for: • Authentication • User properties • etc.
  • 12. dexterity.membrane • Behavior to turn a content type into a member. • Takes care of: ◦ Name content item based on person's first/last name. ◦ Authentication ◦ Provide fullname and bio properties to Plone ◦ Allow the user to edit their profile ◦ Password resets • Only requirement is your content type must have these fields: ◦ first_name, last_name, homepage, bio, password
  • 13. Membrane: the ugly • extra catalog with unneeded indexes
  • 14. The member profile workflow Requirement: Users can choose whether or not their profiles are public. Solution: A boolean in the member schema, plus an auto-triggering workflow.
  • 15. Auto-triggering workflow Two states: • membersonly • private Plus an initial state, "autotrigger". Plus two automatic transitions out of the autotrigger state.
  • 16. Automatic workflow transitions • Fires after any manual workflow transition. • Doesn't show up in the workflow menu. Example from the workflow definition: <transition transition_id="auto_to_private" new_state="private" title="Members only" trigger="AUTOMATIC" before_script="" after_script=""> > <guard> <guard-expression> <guard-expression>not:object/@@netimpact-utils/is_contact_publishable</guard-expres </guard-expres </guard> </transition>
  • 17. The workflow transition trigger We need a manual transition to make the automatic magic happen! @grok.subscribe(IContact, IObjectModifiedEvent) def trigger_contact_workflow(contact, event): wtool = getToolByName(contact, 'portal_workflow') wtool.doActionFor(contact, 'autotrigger')
  • 19. Multi-level workflow Requirement: Any content can be designated as public, private, or visible to two levels of member (free & paid). Specific instance: The member directory is only accessible to members. Solution: custom default workflow.
  • 20. The two_level_member_workflow Most content can be assigned one of these states: • Private - visible to Net Impact staff only • Premium - visible to paid members only • Members-only - visible to members and supporting (paid) members • Public - visible to anyone
  • 21. Roles These levels of access are modeled using 3 built-in roles: • Site Administrator (for staff) • Member (for free members) • Anonymous (for the public) And one custom role: • Paid Member
  • 22. Granting the correct roles based on member status Membrane lets us assign custom roles using an IMembraneUserRoles adapter: class ContactRoleProvider ContactRoleProvider(grok.Adapter, MembraneUser): grok.context(IContact) grok.implements(IMembraneUserRoles) def __init__(self, context): self.context = context def getRolesForPrincipal(self, principal, request=None): roles = [] if self.context.is_staff: roles.append('Site Administrator') roles.append('Member') if self.context.member_status in ('Premium', 'Lifetime'): roles.append('Paid Member') return roles
  • 23. Registration and profile editing Requirement: Multi-part profile editing form with overlays. Solution: Lots of z3c.form forms based on the content model.
  • 24.
  • 25.
  • 26. XML model In part: <model xmlns="http://namespaces.plone.org/supermodel/schema" xmlns:form="http://namespaces.plone.org/supermodel/form">> <schema> <fieldset name="links" label="Links">> <field name="homepage" type="zope.schema.ASCIILine" form:validator="netimpact.content.validators.URLValidator"> > <title> <title>Personal Website</title> </title> <description> <description>Include http://</description> </description> <required> <required>False</required> </required> </field> <field name="twitter" type="zope.schema.TextLine" form:omitted="true"> > <title> <title>Twitter</title> </title> <description> <description>Enter your twitter id (e.g. netimpact)</description> </description> <required> <required>False</required> </required> </field> </fieldset> </schema> </model>
  • 27. Connecting the model to a concrete schema We want to use a schema called IContact, not whatever Dexterity generates for us. In interfaces.py: from zope.interface import alsoProvides from plone.directives import form from zope.app.content.interfaces import IContentType class IContact IContact(form.Schema): form.model('models/contact.xml') alsoProvides(IContact, IContentType) In profiles/default/types/netimpact.contact.xml: <property name="schema">netimpact.content.interfaces.IContact</property> > </property>
  • 28. Using that schema to build a form Unusual requirements: • We have multiple forms with different fields, so can't use autoform. • Late binding of the model means we have to defer form field setup. from plone.directives import dexterity from netimpact.content.interfaces import IContact class EditProfileNetworking EditProfileNetworking(dexterity.EditForm): grok.name('edit-networking') label = u'Networking' # avoid autoform functionality def updateFields(self): pass @property def fields(self): return field.Fields(IContact).select('homepage', 'company_homepage', 'twitter', 'linkedin')
  • 32. Searching the member directory Requirement: Members get access to a member directory searchable by keyword, chapter, location, job function, issue, industry, or sector. Solution: eea.facetednavigation
  • 33.
  • 34. Custom listings for members Requirement: Members show in listings with custom info (school or company and location). Solution: • Override folder_listing • Make search results use folder_listing
  • 35. Synchronizing content with Salesforce.com Requirement: Manage and report on members in Salesforce, present the directory on the web. Solution: Nightly data sync.
  • 37. Contact schema with Salesforce metadata <model xmlns="http://namespaces.plone.org/supermodel/schema" xmlns:form="http://namespaces.plone.org/supermodel/form" xmlns:sf="http://namespaces.plone.org/salesforce/schema">> <schema sf:object="Contact" sf:container="/member-directory" sf:criteria="Member_Status__c != null"> > <field name="email" type="zope.schema.ASCIILine" form:validator="netimpact.content.validators.EmailValidator" security:read-permission="cmf.ModifyPortalContent" sf:field="Email"> > <title> <title>E-mail Address</title> </title> </field> </schema> </model> Performs a query like: SELECT Id, Email FROM Contact WHERE Member_Status__c != null
  • 38. Extending Dexterity schemas Parameterized behavior. • Storage: Schema tagged values • In Python schemas: new grok directives • In XML model: new XML directives in custom namespace • TTW: Custom views to edit the tagged values
  • 39. Field with custom value converter We wanted to convert Salesforce Ids of Chapters into the Plone UUID of corresponding Chapter items: <field name="chapter" type="zope.schema.Choice" form:widget="netimpact.content.browser.widgets.ChapterFieldWidget" sf:field="Chapter__c" sf:converter="uuid"> > <title> <title>Chapter</title> </title> <description></description> <vocabulary> <vocabulary>netimpact.content.Chapters</vocabulary> </vocabulary> <required> <required>True</required> </required> <default> <default>n/a</default> </default> </field>
  • 40. Custom value converters The converter: from collective.salesforce.behavior.converters import DefaultValueConverter class UUIDConverter UUIDConverter(DefaultValueConverter, grok.Adapter): grok.provides(ISalesforceValueConverter) grok.context(IField) grok.name('uuid') def toSchemaValue(self, value): if value: res = get_catalog().searchResults(sf_object_id=value) if res: return res[0].UID
  • 41. Handling collections of related info Education list of dicts in main Contact schema: <field name="education" type="zope.schema.List" form:widget="collective.z3cform.datagridfield.DataGridFieldFactory" sf:relationship="Schools_Attended__r"> > <title> <title>Most Recent School</title> </title> <required> <required>True</required> </required> <min_length> </min_length> <min_length>1</min_length> <value_type type="collective.z3cform.datagridfield.DictRow"> > <schema> <schema>netimpact.content.interfaces.IEducationInfo</schema> </schema> </value_type> </field>
  • 42. The subschema IEducationInfo is another model-based schema: from plone.directives import form class IEducationInfo IEducationInfo(form.Schema): form.model('models/education_info.xml') <model xmlns="http://namespaces.plone.org/supermodel/schema" xmlns:form="http://namespaces.plone.org/supermodel/form" xmlns:sf="http://namespaces.plone.org/salesforce/schema">> <schema sf:object="School_Attended__c" sf:criteria="Organization__c != '' ORDER BY Graduation_Date__c asc NULLS LAST"> > <field name="school_id" type="zope.schema.TextLine" sf:field="Organization__c"> > <title> <title>School ID</title> </title> <required> <required>False</required> </required> </field> </schema> </model> SELECT Id, (SELECT Organization__c FROM School_Attended__c) FROM Contact SELECT
  • 43. Writing back to Salesforce Handled less automatically, in response to an ObjectModifiedEvent: @grok.subscribe(IContact, IObjectModifiedEvent) def save_contact_to_salesforce(contact, event): if not IModifiedViaSalesforceSync.providedBy(event): upsertMember(contact)
  • 44. Handling payments Requirement: Accept payments for: • Several types of membership • Conference registration • Conference expo exhibitors • Chapter dues Solution: groundwire.checkout
  • 46. Pieces of GetPaid groundwire.checkout reuses • Core objects (cart and order storage) • Payment processing code (Authorize.net) • Compatible with getpaid.formgen and pfg.donationform
  • 47. What groundwire.checkout provides • Single-page z3c.form-based checkout form with: ◦ cart listing, ◦ credit card info fieldset ◦ billing address fieldset ◦ much, much easier to customize than PloneGetPaid's • Order confirmation view with summary of the completed transaction • Agnostic as to how items get added to the cart; only handles checkout • API for performing actions after an item is purchased
  • 48. Basic example Add an item to the cart and redirect to checkout: from getpaid.core.item import PayableLineItem from groundwire.checkout.utils import get_cart from groundwire.checkout.utils import redirect_to_checkout item = PayableLineItem() item.item_id = 'item' item.name = 'My Item' item.cost = float(5) item.quantity = 1 cart = get_cart() if 'item' in cart: del cart['item'] cart['item'] = item redirect_to_checkout()
  • 49. Performing actions after purchase Custom item classes can perform their own actions: from getpaid.core.item import PayableLineItem class MyLineItem MyLineItem(PayableLineItem): def after_charged(self): print 'charged!'
  • 50. Pricing Products are managed in Salesforce. But we need to determine the constituency (and thus the price) in Plone.
  • 52. Discounts • Auto-apply vs. coded discounts
  • 53. Mixed theming approach • Diazo without a theme <theme if-content="false()" href="theme.html" /> <!-- Add the site slogan after the logo (example rule with XSLT) --> <replace css:content="#portal-logo"> > <xsl:copy-of css:select="#portal-logo" /> <p id="portal-slogan">Where good works.</p> > </p> </replace> • z3c.jbot to make changes to templates
  • 54. Edit bar at top <replace css:content="#visual-portal-wrapper"> > <xsl:copy-of css:select="#edit-bar" /> <div id="visual-portal-wrapper">> <xsl:apply-templates /> </div> </replace> <replace css:content="#edit-bar" />
  • 55. Tile-based layout <div class="tile-placeholder" tal:attributes="data-tile-href string:${portal_url}/ @@groundwire.tiles.richtext/login-newmember-features" />
  • 57. What Plone could do • Rewrite the password reset tool • Better support for multiple levels of membership • Easier way to customize a type's listing view • Asynchronous processing infrastructure • Built-in support for tiles
  • 58. What Dexterity could do • Make it possible to parameterize widgets and validators in the model • Better way to make multiple forms based on the same schema • Expand the through-the-web editor
  • 59. What Plone gives for free (or cheap) Plone was absolutely the right tool for the job. • Basic content management • Custom form creation using PloneFormGen • Fine-grained access control • Collections • Basic content types
  • 61. Contact me David Glick dglick@groundwireconsulting.com Groundwire Consulting http://groundwireconsulting.com