Time for action editing an instance

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:

Time for action editing an instance

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.

What just happened?

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.

Note

The add and edit parameters to index() are different from the ones passed to __init__().

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.226.66