Login

Another Multiform

Author:
dhke
Posted:
August 27, 2014
Language:
Python
Version:
1.6
Tags:
models forms multiform
Score:
0 (after 0 ratings)

MultiForm and MultiModelForm

Based on a PrefixDict class I wrote and thus very lean. Lacks a little documentation, though

  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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
class PrefixSet(object):
	"""
		Implementation of prefix trees.

		Takes as a list of strings an constructs a prefix tree.
		Nodes in the tree are either a string, representing once of the initial items
		or a tuple with the first item the current prefix, followed by any items
		with that prefix.

		Each level of the tree will only contain prefixes of the same length.

		For example, given the input ('aaa', 'aab', 'ab'), you will get
		(('aa', (('aaa', 'aaa'), ('aac', 'aac'))), ('ab', 'ab'))

		Searching
	"""
	def __init__(self, items):
		self.tree = self.build_tree(items)

	def build_tree(self, items):
		if not items:
			return None
		else:
			buckets = list(items)
			for bucket in buckets:
				if not isinstance(bucket, six.string_types):
					raise ValueError("'{0}' is not string or unicode".format(bucket))
			buckets.sort()
			self.buckets = buckets
			# insert prefix element
			buckets.insert(0, '')
			return self.split_bucket(buckets, pos=0)[1]

	def split_bucket(self, bucket, pos=0):
		if len(bucket) == 2:
			return tuple(bucket)
		else:
			prefix = bucket[0]
			buckets = []
			cur_bucket = None
			cur_char = None
			for item in bucket[1:]:
				try:
					item[pos]
				except IndexError:
					raise ValueError("{0} share the same prefix '{1}'".format(bucket[1:], prefix))
				if cur_char and cur_char == item[pos]:
					cur_bucket.append(item)
				else:
					cur_char = item[pos]
					cur_bucket = [prefix + item[pos], item]
					buckets.append(cur_bucket)

			if len(buckets) == 1:
				split_buckets = self.split_bucket(buckets[0], pos=pos + 1)
			else:
				split_buckets = tuple(self.split_bucket(bucket, pos=pos + 1) for bucket in buckets)
			if not isinstance(split_buckets[0], six.string_types):
				return (prefix, split_buckets)
			else:
				return split_buckets

	def find_prefix(self, string):
		"""
			Find the prefix in the prefix tree that "string" starts with.

			If the prefix was found, returns a tuple (prefix, remainder_of_string)
			where remainder_of_string is the part of the input string
			Returns None, if the prefix was not found.
		"""
		tree = self.tree
		if not self.tree:
			raise KeyError("Prefix set is empty")

		while True:
			if isinstance(tree, six.string_types):
				if string.startswith(tree):
					return (tree, string[len(tree):])
				else:
					raise KeyError("Prefix not found for '{0}'".format(string))
			else:
				# XXX - this is lazy so that bisect works, it can be done without
				# Also, it is possible to keep track of the already matched prefix-prefix
				# while descending the tree and reduce comparison effort.
				prefixes = [bucket[0] for bucket in tree]
				idx = bisect.bisect(prefixes, string) - 1
				if idx < 0:
					raise KeyError("No unique prefix found for '{0}', needs to starth with one of {1}".format(string, prefixes))
				tree = tree[idx][1]


class PrefixViewDict(DictMixin):
	"""
		A dictionary that shows a view of the provided base_dict
		showing only items whose keys start with the specified prefix.
	"""
	def __init__(self, base_dict, prefix, sep=''):
		self.base_dict = base_dict
		self.sep = ''
		self.prefix = prefix

	def make_key(self, key):
		return self.prefix + self.sep + key

	def __getitem__(self, key):
		return self.base_dict[self.make_key(key)]

	def __setitem__(self, key, value):
		self.base_dict[self.make_key(key)] = value

	def __delitem__(self, key, value):
		del self.base_dict[self.make_key(key)]

	def __contains__(self, key):
		return self.make_key(key) in self.base_dict

	def __iter__(self):
		seppre = self.make_key('')
		return (self.make_key(key) for key in self.base_dict if key.startswith(seppre))

	def iteritems(self):
		seppre = self.make_key('')
		return ((self.make_key(key), value) for key, value in self.base_dict.iteritems() if key.startswith(seppre))

	def keys(self):
		return list(self.iterkeys())

	def items(self):
		return list(self.iteritems())

	def values(self):
		return list(self.itervalues())


class PrefixCombiningDict(DictMixin):
	"""
		A dictionary that wraps multiple backend dictionaries with a prefix.

		``base_dicts'' must be itself a dictionary mapping string (or unicode) to dictionary-alike
		objects.

		For example, given

		PrefixCombiningDict({'a': {'z': 1, 'y': 2}, 'b': {'x': 3, 'w': 4}})

		yields a dictionary

		{'az': a, 'ay': 2, 'bx': 3, 'bw': 4}
	"""
	def __init__(self, base_dicts, sep=''):
		if not hasattr(base_dicts, 'iteritems'):
			base_dicts = dict(base_dicts)
		self.base_dicts = base_dicts
		self.sep = sep
		self.prefix_set = PrefixSet(base_dicts)

	def make_key(self, prefix, key):
		return prefix + self.sep + key

	def __getitem__(self, key):
		prefix, sub_key = self.prefix_set.find_prefix(key)
		if sub_key.startswith(self.sep):
			return self.base_dicts[prefix][sub_key[len(self.sep):]]
		else:
			raise KeyError("{0} not found in dictionary".format(key))

	def __setitem__(self, key, item):
		prefix, sub_key = self.prefix_set.find_prefix(key)
		self.base_dicts[prefix][sub_key] = item

	def __delitem__(self, key):
		prefix, sub_key = self.prefix_set.find_prefix(key)
		del self.base_dicts[prefix][sub_key]

	def __contains__(self, key):
		prefix, sub_key = self.prefix_set.find_prefix(key)
		return sub_key in self.base_dicts[prefix]

	def __iter__(self):
		return (self.make_key(prefix, sub_key) for prefix, base_dict in self.base_dicts.iteritems() for sub_key in base_dict)

	def iteritems(self):
		return chain(
			(self.make_key(prefix, sub_key), value) for prefix, base_dict in self.base_dicts.iteritems() for sub_key, value in base_dict.iteritems()
		)

	def keys(self):
		return list(self.iterkeys())

	def items(self):
		return list(self.iteritems())

	def values(self):
		return list(self.itervalues())


class MultiForm(BaseForm):
	base_fields = {}

	def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
		initial=None, error_class=ErrorList, label_suffix=None,
		empty_permitted=False, **kwargs
	):
		super(MultiForm, self).__init__(data=data, files=files,
			auto_id=auto_id, prefix=prefix, initial=initial, error_class=error_class, label_suffix=label_suffix,
			empty_permitted=empty_permitted
		)
		self._init_subforms(data=data, initial=initial, label_suffix=label_suffix, **kwargs)
		self.fields = PrefixCombiningDict(((prefix, form.fields) for prefix, form in self.forms.iteritems() if form.fields), sep='-')

	def _init_subforms(self, data=None, files=None, auto_id='id_%s',
		prefix=None, initial=None, error_class=ErrorList, label_suffix=None,
		empty_permitted=False, **kwargs
	):
		if not prefix:
			prefix = ''

		form_classes = self.get_form_classes()
		if len(form_classes) == 0:
			raise ValueError("Need at least one form class for MultiForm")

		forms = {}
		for key, form_class in form_classes.iteritems():
			sub_initial = initial.get(key) if initial else None
			sub_prefix = prefix + key
			forms[key] = form_class(data=data, files=files, auto_id=auto_id,
				prefix=sub_prefix, initial=sub_initial, error_class=error_class,
				label_suffix=label_suffix, empty_permitted=empty_permitted
			)
		self.forms = forms

	def get_form_classes(self):
		try:
			return self.form_classes
		except NameError:
			raise NameError('Subclasses must set form_classes or override get_form_classes()')

	def __unicode__(self):
		return mark_safe(u"\n".join(unicode(form) for form in self.forms.itervalues()))

	def __str__(self):
		return mark_safe("\n".join(str(form) for form in self.forms.itervalues()))

	def __getitem__(self, key):
		return PrefixCombiningDict(((prefix, form) for prefix, form in self.forms.iteritems() if form), sep='-')[key]


class ModelMultiForm(MultiForm):
	def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
		initial=None, error_class=ErrorList, label_suffix=None,
		empty_permitted=False, instance=None, **kwargs
	):
		super(ModelMultiForm, self).__init__(
			data=data, files=files, auto_id=auto_id, prefix=prefix,
			initial=initial, error_class=error_class, label_suffix=None,
			empty_permitted=empty_permitted, instance=instance, **kwargs
		)

	def _init_subforms(self, data=None, files=None, auto_id='id_%s',
		prefix=None, initial=None, error_class=ErrorList, label_suffix=None,
		empty_permitted=False, instance=None, **kwargs
	):
		form_classes = self.get_form_classes()
		if len(form_classes) == 0:
			raise ValueError("Need at least one form class for MultiForm")

		forms = {}
		for key, form_class in form_classes.iteritems():
			sub_initial = initial.get(key) if initial else None
			sub_instance = instance.get(key) if instance else None
			forms[key] = form_class(data=data, prefix=key, initial=sub_initial, instance=sub_instance)
		self.forms = forms

	@property
	def instance(self):
		return dict((prefix, form.instance) for prefix, form in self.forms.iteritems())

	def save(self, commit=True):
		return dict((key, form.save(commit=commit)) for key, form in self.forms.iteritems())

More like this

  1. . by jeremydw 5 years ago
  2. madslug by catellar 4 years ago
  3. templatetag by faruk 8 months ago
  4. highlight text by nathantn 1 year ago
  5. watermark by dingdongquan 4 years, 8 months ago

Comments

luzfcb (on October 24, 2014):

create a simple example of how to use it

#

Please login first before commenting.