Recalling the implementation of the index view for ILayout, one will remember the way it sets a mode depending on request variables which may have been part of the request.

class Layout(grok.View):
    ''' Renders the base HTML page layout for the site.
        Since for this site, editing, adding and deleting are
        going to be common actions, we provide for these actions as
        URL arguments.  Another approach would have been to use
        traversers, and make the operations part of the URL itself.
    '''
    grok.context(ILayout)
    grok.name('index')

    editing = False
    adding = False
    deleting = False
    viewing = True

    def update(self, edit=None, add=None, delete=None, nomce=None):
        self.editing = edit is not None
        self.adding = add is not None
        self.deleting = delete is not None
        self.viewing = not (self.editing or self.adding or self.deleting)
        style.need()
        favicon.need()
        if nomce is None:  # Switch includes or omits tinyMCE
            tinymce.need()
        textdivs.need()

Note how the view sets instance attributes for self.editing, self.adding, self.deleting and self.viewing.  This is important when we look at the Content viewlet manager for an article:

class TextViewlet(grok.Viewlet):
    ''' Render the article content
    '''
    grok.context(IArticle)
    grok.viewletmanager(Content)

...and the corresponding template...

<div class='centered' tal:condition='viewlet/view/editing' 
                      tal:content='structure context/@@edit' />
<div class='centered' tal:condition='viewlet/view/adding' 
                      tal:content='structure context/@@add' />
<div class='centered' tal:condition='viewlet/view/deleting' 
                      tal:content='structure context/@@delete' />
<div class='vscroll text' tal:condition='viewlet/view/viewing' 
                          tal:content="structure context/text" />h

The template refers to the state attributes for the view, and decides which div to render.  While there are numerous other approaches that would work as well, this one is simple and coincidentally manages to demonstrate conditional code in page templates.  The tal:content="structure context/@@edit" and similar lines are effectively path expressions which render the views called edit, add or delete for the current context.  So this demonstrates how to render other views within a viewlet.

Overriding Formlib

The edit, add or delete views under discussion are forms which will be automatically generated by the grok.formlib library, which uses fields derived from schema definitions to generate input widgets. 

The default template for a grok.EditForm and grok.AddForm assumes that the HTML will be viewed as an independent document, and so generates the html, head and body tags.  This is both unnecessary and unwanted.

Also, there is a need to persist the state variable (such as 'edit') which was passed as a request variable to the index view.  If we do not include this variable as a hidden input in our rendered form, subsequent form submission would post data which never reaches the form handler.  Why? because the view mode would never get set, and the wrong view would be rendered without the correct mode.

So the above two needs suggest that we create our own specialisation of a grok.EditForm and grok.AddForm to deal with these issues. We can find appropriate code to do this in forms.py

"""  A small shim over grok.formlib forms to add a default hidden() method and
     replace the default form template
"""
import grok

class EditForm(grok.EditForm):
    grok.template('edit')
    grok.baseclass()

    def hidden(self): return []

class AddForm(grok.AddForm):
    grok.template('edit')
    grok.baseclass()

    def hidden(self): return []

  These two classes use grok.baseclass() to ensure that Grok does not try to configure them.  Both EditForm and AddForm use a replacement template, namely edit.pt.

<form tal:attributes="id view/formId | nothing; 
                      action view/getURL | request/URL" method="post"
      class="edit-form" enctype="multipart/form-data">

  <tal:loop tal:repeat="field view/hidden">
	  <input type='hidden' tal:attributes="name field/name; value field/value"/>
  </tal:loop>

  <fieldset class="form-fieldset">

  <legend i18n:translate=""
    tal:condition="view/label"
    tal:content="view/label">Label</legend>

  <div class="form-status"
    tal:define="status view/status"
    tal:condition="status">

    <div i18n:translate="" tal:content="view/status">
      Form status summary
    </div>

    <ul class="errors" tal:condition="view/errors">
      <li tal:repeat="error view/error_views">
         <span tal:replace="structure error">Error Type</span>
      </li>
    </ul>
  </div>


  <div class="form-fields">
  	<table>
      <tal:block repeat="widget view/widgets">
        <tr class="form-widget">
          <td class="label" tal:define="hint widget/hint">
            <label tal:condition="python:hint"
                   tal:attributes="for widget/name; title hint">
              <span class="required" tal:condition="widget/required"
              >*</span><span i18n:translate=""
                             tal:content="widget/label">label</span>
            </label>
            <label tal:condition="python:not hint"
                   tal:attributes="for widget/name">
              <span class="required" tal:condition="widget/required"
              >*</span><span i18n:translate=""
                             tal:content="widget/label">label</span>
            </label>
          </td>
          <td class="field">
            <div class="widget" tal:content="structure widget">
              <input type="text" />
            </div>
            <div class="error" tal:condition="widget/error">
              <span tal:replace="structure widget/error">error</span>
            </div>
            <div class="widgetDisplay" tal:condition="widget/display | nothing"
            	tal:content="structure widget/display">
            </div>
          </td>
        </tr>
      </tal:block>
  	</table>
  </div>
  <input type='hidden' id='camefrom' name='camefrom' 
                tal:condition='view/referrer | nothing'
  		tal:attributes='value view/referrer' />
  <div id="actionsView">
    <span class="actionButtons" tal:condition="view/availableActions">
      <input tal:repeat="action view/actions"
             tal:replace="structure action/render" />
    </span>
  </div>
  </fieldset>
</form>

 

Changing the default form template is not the only way to produce a hidden input inside a form.  Another approach which one might have followed is to define a new schema field type and add the field to the model itself.

CRUD Forms

The actual forms used to add or edit articles for our wiki application derive from the base classes defined above.

First, the Add Form:

class Add(forms.AddForm):
    ''' Renders the article add form, which is similar to the edit form
    '''
    grok.context(IArticle)
    form_fields = grok.Fields(IArticle).omit('text')

    def hidden(self): # persists the request 'add' form variable
        return [dict(name='add', value='')]

    def setUpWidgets(self, ignore_request=False):
        super(Add, self).setUpWidgets(ignore_request)
        self.widgets['title'].displayWidth=50
        self.widgets['navTitle'].displayWidth=12

    @grok.action(u'Add this page')
    def addItem(self, **data):
        ntitle = quote_plus(data['navTitle'])
        item = self.context[ntitle] = NoobsArticle()
        self.applyData(item, **data)
        self.redirect(self.url(self.context))

The Edit Form:

class Edit(forms.EditForm):
    '''  Renders the article editor. This includes the tinyMCE HTML editor
    '''
    grok.context(IArticle)

    def hidden(self):  # persists the request 'edit' form variable
        return [dict(name='edit', value='')]

    def setUpWidgets(self, ignore_request=False):
        super(Edit, self).setUpWidgets(ignore_request)
        self.widgets['title'].displayWidth=50
        self.widgets['navTitle'].displayWidth=12

    @grok.action(u'Change this page')
    def changeItem(self, **data):
        title = data['navTitle']
        if self.context.navTitle==title:
            self.applyData(self.context, **data)
            self.redirect(self.url(self.context))
        else:   # to change it we must create new and remove old
            ntitle = quote_plus(title)
            self.context.__parent__[ntitle] = self.context
            navTitle = quote_plus(self.context.navTitle)
            del self.context.__parent__[navTitle]

            self.applyData(self.context, **data)
            self.redirect(self.url(self.context.__parent__[ntitle]))

and finally, the delete form:

class Delete(forms.EditForm):
    '''  A Delete confirmation form and action
    '''
    grok.context(NoobsArticle)       # for normal articles
    form_fields = grok.AutoFields(NoobsArticle).omit('text')

    def setUpWidgets(self, ignore_request=False):
        self.form_fields['title'].field.readonly = True
        self.form_fields['navTitle'].field.readonly = True
        super(Delete, self).setUpWidgets(ignore_request)
        self.form_fields['title'].field.readonly = False
        self.form_fields['navTitle'].field.readonly = False

    def hidden(self): # persists the request 'delete' form variable
        return [dict(name='delete', value='')]

    @grok.action(u'Delete this page (cannot be undone)')
    def delPage(self, **_data):
        title = self.context.navTitle
        try:
            ntitle = quote_plus(title)
            parent = self.context.__parent__
            del self.context.__parent__[ntitle]
            self.redirect(self.url(parent))
        except Exception, e:
            raise e

 With the Add form, we omit the text from the form, since the article editor also supports the abilty to add resources such as graphics or syntax highlighted text.  The problem with this, is that the article does not exist until after it is created, and so can contain no links to resources within itself.

In each of the forms, a method caled hidden() returns a dict containing the list of names and values for inclusion of state as hidden inputs in the rendered form.

The Delete form, like the Add form does not include text. In addition, it changes the fields to read-only by defining the SetUpWidgets method, and restores the readonly flag to False after calling the superclass SetUpWidgets method.  This causes the widget to render as text rather than as an entry box.

Note: The use of SetUpWidgets is not the only way to control field properties, although with AutoFields it may be.  If instead, one uses the grok.Fields() call, one may do grok.Fields(Interface, for_display=True).omit(...) . The difference is that grok.AutoFields() examines the model for all interfaces and all fields required by those interfaces, where grok.Fields() is more of a manual thing.

 

Grok 4 Noobs

Providing for article addition, modification and deletion