We also want to open a form that will allow the user to edit an existing instance when double-clicked in the browse list. In the stripped down application that we created in the previous section, we merely created the ContactBrowser
class as a subclass of Browse
. If we want to add an additional double-click handler to the browse list element, we will have to override the index()
method.
In the definition of the ContactBrowser
class, add the following to the definition of the index()
method (the complete code is available as crmcontactedit.py):
Chapter8/crmcontactedit.py
@cherrypy.expose def index(self, _=None, start=0, pattern=None, sortorder=None, cacheid=None, next=None, previous=None, first=None, last=None, clear=None): s="".join(super().index(_, start, pattern, sortorder, cacheid, next,previous, first, last, clear)) s+=''' <script> $("table.entitylist tr").dblclick(function(){ var id=$(this).attr('id'), $("body").load('edit/?id='+id); }); </script> ''' return basepage%s
The code merely gathers the output from the original index()
method of the Browse
class (highlighted) and adds a<script>
element to it that will add a double-click handler to each<tr>
element in the browse list. This click handler will replace the body with the form served by the edit URL, which will be passed an id
parameter equal to the id
attribute of the<tr>
element.
If you run crmcontactedit.py
, you will be presented with the same list of contacts as before and if it is empty, you may first need to add one or more contacts. Once these are present, you can double-click on any of them to be presented with an edit screen:
This looks very similar to the add screen, but changing values here and clicking the Edit button will alter instead of adding a contact and returning you to the list of contacts.
Let us have a look at how the Display
class handles the editing of instances.
All interaction by an instance of the Display
class is provided by a single method: index()
(full code is available in display.py):
Chapter8/display.py
@cherrypy.expose def index(self, id=None, _=None, add=None, edit=None, related=None, **kw): mount = cherrypy.request.path_info if not id is None : id = int(id) kv=[] submitbutton="" if edit or add: ... code to process the results of an edit/add form omitted action="display" autocomplete='' if not id is None: e=self.entity(id=id) for c in self.columns: if c in self.entity.columns: kv.append('<label for="%s">%s</label>'% (c,self.entity.displaynames[c])) if c in self.entity.validators and type( self.entity.validators[c])==Picklist: kv.append('<select name="%s">'%c) kv.extend(['<option %s>%s</option>'% ("selected" if v==getattr(e,c) else "",k) for k,v in self.entity.validators[c] .list.items()]) kv.append('</select>') else: kv.append( '<input type="text" name="%s" value="%s">'% (c,getattr(e,c))) elif issubclass(c,AbstractEntity): kv.append( '<label for="%s">%s</label>'% (c.__name__,c.__name__)) v=",".join([r.primary for r in e.get(c)]) kv.append( '<input type="text" name="%s" value="%s">'% (c.__name__,v)) autocomplete += ''' $("input[name=%s]").autocomplete({source:"%s",minLength:2}); '''%(c.__name__, mount+'autocomplete?entity='+c.__name__) yield self.related_entities(e) if self.edit: action='edit' submitbutton=''' <input type="hidden" name="id" value="%s"> <input type="hidden" name="related" value="%s,%s"> <input type="submit" name="edit" value="Edit"> '''%(id,self.entity.__name__,id) elif self.add: action='add' for c in self.columns: if c in self.entity.columns: kv.append('<label for="%s">%s</label>'%( c,self.entity.displaynames[c])) if c in self.entity.validators and type( self.entity.validators[c])==Picklist: kv.append('<select name="%s">'%c) kv.extend(['<option>%s</option>'%v for v in self.entity.validators[c]. list.keys()]) kv.append('</select>') else: kv.append('<input type="text" name="%s">' %c) elif c=="related": pass elif issubclass(c,AbstractEntity): kv.append('<label for="%s">%s</label>'% (c.__name__,c.__name__)) kv.append('<input type="text" name="%s">'% c.__name__) autocomplete += ''' $("input[name=%s]").autocomplete({source:"%s",minLength:2}); '''%(c.__name__, mount+'autocomplete?entity='+c.__name__) submitbutton=''' <input type="hidden" name="related" value="%s"> <input type="submit" name="add" value="Add"> '''%related else: yield """cannot happen id=%s, edit=%s, add=%s, self.edit=%s, self.add=%s """%(id,edit,add,self.edit,self.add) yield '<form action="%s">'%action for k in kv: yield k yield submitbutton yield "</form>" yield '<script>'+autocomplete+'</script>'
Depending on the parameters passed to the index() method and the information stored when the Display
instance was initialized, index()
performs different but similar actions.
When called without the add
or edit
parameter, index()
is called to display, edit, or add an instance and the first part of the code is skipped.
First, we check if the id
parameter is present (highlighted). If not, we're expected to present an empty form to let the user enter the attributes for an all new instance. However, if an id
parameter is present, we have to display a form with values.
To present such a form, we retrieve the entity with the given ID and check which columns we have to display and see if such a column is an attribute of the entity we are dealing with (highlighted). If so, we append to the kv
list a<label>
element with the display name of the column and an<input>
or<select>
element, depending on whether we are dealing with an unrestricted text field or a picklist. If we are dealing with a picklist, the available choices are added as<option>
elements.
If the column to display is not an attribute of the entity but another entity class, we are dealing with a relation. Here we also add a<label>
element and an<input>
field, but we also add JavaScript code to the autocomplete
variable that when executed will convert this<input>
element into an autocomplete widget, which will retrieve its choices from the autocomplete()
method in this same Display
instance.
Only if this Display
instance was initialized to perform the edit function (highlighted), we append a submit button and set the action
variable to edit (the last part of the URL the values of the<form>
element will be submitted to). We also add an extra hidden input element that holds the ID of the instance we are editing.
Constructing the empty form to add a new instance is almost the same exercise, only in this case, no values are filled in any of the<input>
elements.
The final lines of code (highlighted) are shared again and used to deliver the<form>
element based on the components just created for either an edit/display form or an empty add form together with any JavaScript generated to implement the autocomplete features. A typical sample of HTML generated for an edit form may look like this:
<form action="edit"> <label for="firstname">First Name</label> <input name="firstname" value="Eva" type="text"> <label for="lastname">Last Name</label> <input name="lastname" value="Johnson" type="text"> <label for="gender">Gender</label> <select name="gender"> <option selected="selected">Unknown</option> <option>Male</option> <option>Female</option> </select> <label for="telephone">Telephone</label> <input name="telephone" value="" type="text"> <label for="Account">Account</label> <input name="Account" value="" type="text"> <label for="Address">Address</label> <input name="Address" value="" type="text"> <input name="id" value="2" type="hidden"> <input name="edit" value="Edit" type="submit"> </form> <script> $("input[name=Account]").autocomplete({source:"autocomplete?entity= Account",minLength:2}); $("input[name=Address]").autocomplete({source:"autocomplete?entity= Address",minLength:2}); </script>
If the index()
method of Display
is called with either the add
or the edit
parameter present (typically the result of clicking a submit button in a generated edit or add form), different code is executed:
Chapter8/display.py
@cherrypy.expose def index(self, id=None, _=None, add=None, edit=None, related=None, **kw): mount = cherrypy.request.path_info if not id is None : id = int(id) kv=[] submitbutton="" if edit or add: if (edit and add): raise HTTPError(500) if not self.logon is None: username=self.logon.checkauth() if username is None: raise HTTPRedirect('/') if add: attr={} cols={} relations={c.__name__:c for c in self.columns if type(c)!=str} for k,v in kw.items(): if not k in self.entity.columns: attr[k]=v if not k in relations : raise KeyError(k, 'not a defined relation') else: cols[k]=v e=self.entity(**cols) for k,v in attr.items(): if v != None and v != '': relentity = relations[k] primary = relentity.primaryname rels = relentity.listids( pattern=[(primary,v)]) if len(rels): r = relentity(id=rels[0]) else: r = relentity(**{primary:v}) e.add(r) if not related is None and related != '': r=related.split(',') re=e.relclass[r[0]](id=int(r[1])) e.add(re) redit = sub(Display.finaladd,'',mount) raise cherrypy.HTTPRedirect(redit) elif edit: e=self.entity(id=id) e.update(**kw) raise cherrypy.HTTPRedirect( mount.replace('edit','').replace('//','/')) ...code to display and instance omitted
Only one of edit
or add
should be present; if both are present we raise an exception. If the user is not authenticated, editing an instance or adding a new one is not allowed, and we unceremoniously redirect him/her to the homepage (highlighted).
If the add
parameter is present, we will be creating a brand new instance. The first item of order is to check all incoming parameters to see if they are either an attribute of the entity that we will be creating (highlighted) or the name of a related entity. If not, an exception is raised.
The next step is to create the new entity (highlighted) and establish any relations.
18.118.226.66