Javascript Chain Select Widget

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
from django.newforms import *
from django.newforms.widgets import flatatt

def form_decorator(fields = {}, attrs = {}, widgets = {},
	labels = {}, choices = {}):

	"""
	This function helps to add overrides when creating forms from models/instances.
	Pass in dictionary of fields to override certain fields altogether, otherwise
	add widgets or labels as desired.
	
	For example:
	
	class Project(models.Model):
	
			name = models.CharField(maxlength = 100)
			description = models.TextField()
			owner = models.ForeignKey(User)
	
	project_fields = dict(
			owner = None
	)
	
	project_widgets = dict(
			name = forms.TextInput({"size":40}),
			description = forms.Textarea({"rows":5, "cols":40}))
	
	project_labels = dict(
			name = "Enter your project name here"
	)
	
	callback = form_decorator(project_fields, project_widgets, project_labels)
	project_form = forms.form_for_model(Project, formfield_callback = callback)
	
	This saves having to redefine whole fields for example just to change a widget
	setting or label.
	"""
	
	def formfields_callback(f, **kw):
		if f.name in fields:
			# replace field altogether
			field = fields[f.name]
			f.initial = kw.pop("initial", None)
			return field
		if f.name in widgets:
			kw["widget"] = widgets[f.name]
		if f.name in attrs:
			widget = kw.pop("widget", f.formfield().widget)
			if widget :
				widget.attrs.update(attrs[f.name])
				kw["widget"] = widget
		if f.name in labels:
			kw["label"] = labels[f.name]
		if f.name in choices:
			choice_set = choices[f.name]
			if callable(choice_set) : choice_set = choice_set()
			kw["choices"] = choice_set
		return f.formfield(**kw)
	return formfields_callback

class ChainSelectWidget(Widget):
	#This widget uses javascript to build Chain Selects to
	#narrow down ForeignKey object types in an intuitive manner.
	#It is especially useful when the __str__ of the object direct foreign
	#key isn't necessarily unique, and the parent model of it needs 
	#to be looked at.
	#This code uses the Chained Select javascript written by
	#Xin Yang (http://www.yxscripts.com/)
	#This widget must be used on custom views. I had a VERY hard time
	#trying to get it registered into the form_for_model and 
	#form_for_instance helper functions.
	#example:
	###models.py###
	#class A(models.Model):
	#	name=models.CharField()
	#class B(models.Model):
	#	name=models.CharField()
	#	to_A = models.ForeignKey(A)
	#class C(models.Model):
	#	name=models.CharField()
	#	to_B = models.ForeignKey(B)
	
	###views.py###
	#def test(request):
	#	import A,B,C
	#	from CustomWidgets import *
	#	from django.newforms import form_for_model
	#	from django.shortcuts import render_to_response
	#	widget_overwrite=dict(to_B=ChainSelectWidget(order=[(A, 'name'), (B, 'name'), (C, 'name')]))
	#	callback=form_decorator(widgets=widget_overwrite)
	#	modified_form=form_for_model(C, formfield_callback=callback)()
	# return render_to_response('path/to/template.html', {'form': modified_form})
	
	###template.html###
	#...
	#<head>
	#<script language="javascript" src="path/to/chainedselects.js"></script>
	#</head>
	#...
	#<form>
	#{% for field in form %}
	#{{field.label}}: {{field}}
	#{% endfor %}
	#...
	def __init__(self, attrs=None, order=[]):
		#Order is a list of model objects that define the chain select tree
		#it is a list of tuples.  The first value is the model object, the second
		#value the field to order by
		#eg:
		#	order=[(A, 'name'), (B, 'name'), (C, 'name')]
		self.attrs = attrs or {}
		self.html = ''
		self.order = list(order)

	def _buildjs(self, current=None, backtrail=''):
		if current == None:
			current = self.order[0][0].objects.all().order_by(self.order[0][1])
		if len(current) == 0:
			return ''
		self.html +='addOption("%s", "---------", "", 1);\n'%(backtrail)
		if current[0]._meta.module_name == self.order[-1][0]._meta.module_name:
			for end in current:
				self.html += 'addList("%s", "%s", "%s");\n' % (backtrail, str(end), end._get_pk_val())
		else:
			for (base_model, order_set_by) in self.order:
				if base_model._meta.module_name == current[0]._meta.module_name:
					get_set = self.order[self.order.index((base_model, order_set_by))+1][0]._meta.module_name
			for entry in current:
				self.html += 'addList("%s", "%s", "", "%s__%s");\n' % (backtrail, str(entry), backtrail, str(entry))
				self._buildjs(backtrail='%s__%s'%(backtrail, str(entry)), current=getattr(entry, '%s_set'%(get_set)).all().order_by(order_set_by))

	def render(self, name, value, attrs=None):
		self.html += '<table>\n'
		for entry in self.order:
			self.html += '<tr><td align="right">%s:</td>\n' % entry[0]._meta.module_name.capitalize()
			if entry[0] == self.order[-1][0]:
				final_attrs = self.build_attrs(attrs, name=name)
				self.html += '<td><select %s></select></td></tr>\n' % flatatt(final_attrs)
			else:
				self.html += '<td><select name="chain_to_%s__%s"></select></td></tr>\n' % (name, entry[0]._meta.module_name)
		self.html += '</table>\n'
		self.html += '<script language="javascript">\n'
		self.html += 'var disable_empty_list=true;\n'
		self.html += 'var newwindow=0;\n'
		self.html += 'addListGroup("%s", "%s__%s");\n'%(name, name, self.order[0][0].objects.all()[0]._meta.module_name)
		self._buildjs(current=None, backtrail='%s__%s'%(name, self.order[0][0].objects.all()[0]._meta.module_name))
		self.html += '</script>\n'
		self.html += '<script language="javascript">\n'
		self.html += 'initListGroup("%s", '%name
		for entry in self.order:
			if entry[0] == self.order[-1][0]:
				self.html += 'document.forms[0].%s, ' % name
			else:
				self.html += 'document.forms[0].chain_to_%s__%s, ' % (name, entry[0]._meta.module_name) 
		self.html += '"savestate");\n'
		self.html += '</script>\n'
		return u'%s' % self.html

More like this

  1. YUI Autocomplete by pigletto 6 years, 7 months ago
  2. Dynamically change a form select widget to a hidden widget by epicserve 4 years, 9 months ago
  3. autocompleter with database query by bbolli 6 years, 9 months ago
  4. MultiSelectField with comma separated values (Field + FormField) by quinode 1 year, 11 months ago
  5. Database migration and dump/load script by akaihola 7 years ago

Comments

fnl (on June 3, 2008):

the code covers the description and links to the right - maybe you could put some newlines into the long lines at the end?

#

(Forgotten your password?)