fields.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. # -*- coding: utf-8 -*-
  2. """
  3. Based on django mongotools (https://github.com/wpjunior/django-mongotools) by
  4. Wilson Júnior (wilsonpjunior@gmail.com).
  5. """
  6. import copy
  7. from django import forms
  8. from django.core.validators import (EMPTY_VALUES, MinLengthValidator,
  9. MaxLengthValidator)
  10. try:
  11. from django.utils.encoding import force_text as force_unicode
  12. except ImportError:
  13. from django.utils.encoding import force_unicode
  14. try:
  15. from django.utils.encoding import smart_text as smart_unicode
  16. except ImportError:
  17. try:
  18. from django.utils.encoding import smart_unicode
  19. except ImportError:
  20. from django.forms.util import smart_unicode
  21. from django.utils.translation import ugettext_lazy as _
  22. from django.forms.util import ErrorList
  23. from django.core.exceptions import ValidationError
  24. try: # objectid was moved into bson in pymongo 1.9
  25. from bson.errors import InvalidId
  26. except ImportError:
  27. from pymongo.errors import InvalidId
  28. from mongodbforms.widgets import ListWidget, MapWidget, HiddenMapWidget
  29. class MongoChoiceIterator(object):
  30. def __init__(self, field):
  31. self.field = field
  32. self.queryset = field.queryset
  33. def __iter__(self):
  34. if self.field.empty_label is not None:
  35. yield ("", self.field.empty_label)
  36. for obj in self.queryset.all():
  37. yield self.choice(obj)
  38. def __len__(self):
  39. return len(self.queryset)
  40. def choice(self, obj):
  41. return (self.field.prepare_value(obj),
  42. self.field.label_from_instance(obj))
  43. class NormalizeValueMixin(object):
  44. """
  45. mongoengine doesn't treat fields that return an empty string
  46. as empty. This mixins can be used to create fields that return
  47. None instead of an empty string.
  48. """
  49. def to_python(self, value):
  50. value = super(NormalizeValueMixin, self).to_python(value)
  51. if value in EMPTY_VALUES:
  52. return None
  53. return value
  54. class MongoCharField(NormalizeValueMixin, forms.CharField):
  55. pass
  56. class MongoEmailField(NormalizeValueMixin, forms.EmailField):
  57. pass
  58. class MongoSlugField(NormalizeValueMixin, forms.SlugField):
  59. pass
  60. class MongoURLField(NormalizeValueMixin, forms.URLField):
  61. pass
  62. class ReferenceField(forms.ChoiceField):
  63. """
  64. Reference field for mongo forms. Inspired by
  65. `django.forms.models.ModelChoiceField`.
  66. """
  67. def __init__(self, queryset, empty_label="---------", *args, **kwargs):
  68. forms.Field.__init__(self, *args, **kwargs)
  69. self.empty_label = empty_label
  70. self.queryset = queryset
  71. def _get_queryset(self):
  72. return self._queryset.clone()
  73. def _set_queryset(self, queryset):
  74. self._queryset = queryset
  75. self.widget.choices = self.choices
  76. queryset = property(_get_queryset, _set_queryset)
  77. def prepare_value(self, value):
  78. if hasattr(value, '_meta'):
  79. return value.pk
  80. return super(ReferenceField, self).prepare_value(value)
  81. def _get_choices(self):
  82. return MongoChoiceIterator(self)
  83. choices = property(_get_choices, forms.ChoiceField._set_choices)
  84. def label_from_instance(self, obj):
  85. """
  86. This method is used to convert objects into strings; it's used to
  87. generate the labels for the choices presented by this object.
  88. Subclasses can override this method to customize the display of
  89. the choices.
  90. """
  91. return smart_unicode(obj)
  92. def clean(self, value):
  93. # Check for empty values.
  94. if value in EMPTY_VALUES:
  95. if self.required:
  96. raise forms.ValidationError(self.error_messages['required'])
  97. else:
  98. return None
  99. oid = super(ReferenceField, self).clean(value)
  100. try:
  101. obj = self.queryset.get(pk=oid)
  102. except (TypeError, InvalidId, self.queryset._document.DoesNotExist):
  103. raise forms.ValidationError(
  104. self.error_messages['invalid_choice'] % {'value': value}
  105. )
  106. return obj
  107. def __deepcopy__(self, memo):
  108. result = super(forms.ChoiceField, self).__deepcopy__(memo)
  109. result.queryset = self.queryset # self.queryset calls clone()
  110. result.empty_label = copy.deepcopy(self.empty_label)
  111. return result
  112. class DocumentMultipleChoiceField(ReferenceField):
  113. """A MultipleChoiceField whose choices are a model QuerySet."""
  114. widget = forms.SelectMultiple
  115. hidden_widget = forms.MultipleHiddenInput
  116. default_error_messages = {
  117. 'list': _('Enter a list of values.'),
  118. 'invalid_choice': _('Select a valid choice. %s is not one of the'
  119. ' available choices.'),
  120. 'invalid_pk_value': _('"%s" is not a valid value for a primary key.')
  121. }
  122. def __init__(self, queryset, *args, **kwargs):
  123. super(DocumentMultipleChoiceField, self).__init__(
  124. queryset, empty_label=None, *args, **kwargs
  125. )
  126. def clean(self, value):
  127. if self.required and not value:
  128. raise forms.ValidationError(self.error_messages['required'])
  129. elif not self.required and not value:
  130. return []
  131. if not isinstance(value, (list, tuple)):
  132. raise forms.ValidationError(self.error_messages['list'])
  133. qs = self.queryset
  134. try:
  135. qs = qs.filter(pk__in=value)
  136. except ValidationError:
  137. raise forms.ValidationError(
  138. self.error_messages['invalid_pk_value'] % str(value)
  139. )
  140. pks = set([force_unicode(getattr(o, 'pk')) for o in qs])
  141. for val in value:
  142. if force_unicode(val) not in pks:
  143. raise forms.ValidationError(
  144. self.error_messages['invalid_choice'] % val
  145. )
  146. # Since this overrides the inherited ModelChoiceField.clean
  147. # we run custom validators here
  148. self.run_validators(value)
  149. return list(qs)
  150. def prepare_value(self, value):
  151. if hasattr(value, '__iter__') and not hasattr(value, '_meta'):
  152. sup = super(DocumentMultipleChoiceField, self)
  153. return [sup.prepare_value(v) for v in value]
  154. return super(DocumentMultipleChoiceField, self).prepare_value(value)
  155. class ListField(forms.Field):
  156. default_error_messages = {
  157. 'invalid': _('Enter a list of values.'),
  158. }
  159. widget = ListWidget
  160. hidden_widget = forms.MultipleHiddenInput
  161. def __init__(self, contained_field, *args, **kwargs):
  162. if 'widget' in kwargs:
  163. self.widget = kwargs.pop('widget')
  164. if isinstance(contained_field, type):
  165. contained_widget = contained_field().widget
  166. else:
  167. contained_widget = contained_field.widget
  168. if isinstance(contained_widget, type):
  169. contained_widget = contained_widget()
  170. self.widget = self.widget(contained_widget)
  171. super(ListField, self).__init__(*args, **kwargs)
  172. if isinstance(contained_field, type):
  173. self.contained_field = contained_field(required=self.required)
  174. else:
  175. self.contained_field = contained_field
  176. if not hasattr(self, 'empty_values'):
  177. self.empty_values = list(EMPTY_VALUES)
  178. def validate(self, value):
  179. pass
  180. def clean(self, value):
  181. clean_data = []
  182. errors = ErrorList()
  183. if not value or isinstance(value, (list, tuple)):
  184. if not value or not [
  185. v for v in value if v not in self.empty_values
  186. ]:
  187. if self.required:
  188. raise ValidationError(self.error_messages['required'])
  189. else:
  190. return []
  191. else:
  192. raise ValidationError(self.error_messages['invalid'])
  193. for field_value in value:
  194. try:
  195. clean_data.append(self.contained_field.clean(field_value))
  196. except ValidationError as e:
  197. # Collect all validation errors in a single list, which we'll
  198. # raise at the end of clean(), rather than raising a single
  199. # exception for the first error we encounter.
  200. errors.extend(e.messages)
  201. if self.contained_field.required:
  202. self.contained_field.required = False
  203. if errors:
  204. raise ValidationError(errors)
  205. self.validate(clean_data)
  206. self.run_validators(clean_data)
  207. return clean_data
  208. def _has_changed(self, initial, data):
  209. if initial is None:
  210. initial = ['' for x in range(0, len(data))]
  211. for initial, data in zip(initial, data):
  212. if self.contained_field._has_changed(initial, data):
  213. return True
  214. return False
  215. def prepare_value(self, value):
  216. value = [] if value is None else value
  217. value = super(ListField, self).prepare_value(value)
  218. prep_val = []
  219. for v in value:
  220. prep_val.append(self.contained_field.prepare_value(v))
  221. return prep_val
  222. class MapField(forms.Field):
  223. default_error_messages = {
  224. 'invalid': _('Enter a list of values.'),
  225. 'key_required': _('A key is required.'),
  226. }
  227. widget = MapWidget
  228. hidden_widget = HiddenMapWidget
  229. def __init__(self, contained_field, max_key_length=None,
  230. min_key_length=None, key_validators=[], field_kwargs={},
  231. *args, **kwargs):
  232. if 'widget' in kwargs:
  233. self.widget = kwargs.pop('widget')
  234. if isinstance(contained_field, type):
  235. contained_widget = contained_field().widget
  236. else:
  237. contained_widget = contained_field.widget
  238. if isinstance(contained_widget, type):
  239. contained_widget = contained_widget()
  240. self.widget = self.widget(contained_widget)
  241. super(MapField, self).__init__(*args, **kwargs)
  242. if isinstance(contained_field, type):
  243. field_kwargs['required'] = self.required
  244. self.contained_field = contained_field(**field_kwargs)
  245. else:
  246. self.contained_field = contained_field
  247. self.key_validators = key_validators
  248. if min_key_length is not None:
  249. self.key_validators.append(MinLengthValidator(int(min_key_length)))
  250. if max_key_length is not None:
  251. self.key_validators.append(MaxLengthValidator(int(max_key_length)))
  252. # type of field used to store the dicts value
  253. if not hasattr(self, 'empty_values'):
  254. self.empty_values = list(EMPTY_VALUES)
  255. def _validate_key(self, key):
  256. if key in self.empty_values and self.required:
  257. raise ValidationError(self.error_messages['key_required'],
  258. code='key_required')
  259. errors = []
  260. for v in self.key_validators:
  261. try:
  262. v(key)
  263. except ValidationError as e:
  264. if hasattr(e, 'code'):
  265. code = 'key_%s' % e.code
  266. if code in self.error_messages:
  267. e.message = self.error_messages[e.code]
  268. errors.extend(e.error_list)
  269. if errors:
  270. raise ValidationError(errors)
  271. def validate(self, value):
  272. pass
  273. def clean(self, value):
  274. clean_data = {}
  275. errors = ErrorList()
  276. if not value or isinstance(value, dict):
  277. if not value or not [
  278. v for v in value.values() if v not in self.empty_values
  279. ]:
  280. if self.required:
  281. raise ValidationError(self.error_messages['required'])
  282. else:
  283. return {}
  284. else:
  285. raise ValidationError(self.error_messages['invalid'])
  286. # sort out required => at least one element must be in there
  287. for key, val in value.items():
  288. # ignore empties. Can they even come up here?
  289. if key in self.empty_values and val in self.empty_values:
  290. continue
  291. try:
  292. val = self.contained_field.clean(val)
  293. except ValidationError as e:
  294. # Collect all validation errors in a single list, which we'll
  295. # raise at the end of clean(), rather than raising a single
  296. # exception for the first error we encounter.
  297. errors.extend(e.messages)
  298. try:
  299. self._validate_key(key)
  300. except ValidationError as e:
  301. # Collect all validation errors in a single list, which we'll
  302. # raise at the end of clean(), rather than raising a single
  303. # exception for the first error we encounter.
  304. errors.extend(e.messages)
  305. clean_data[key] = val
  306. if self.contained_field.required:
  307. self.contained_field.required = False
  308. if errors:
  309. raise ValidationError(errors)
  310. self.validate(clean_data)
  311. self.run_validators(clean_data)
  312. return clean_data
  313. def _has_changed(self, initial, data):
  314. for k, v in data.items():
  315. if initial is None:
  316. init_val = ''
  317. else:
  318. try:
  319. init_val = initial[k]
  320. except KeyError:
  321. return True
  322. if self.contained_field._has_changed(init_val, v):
  323. return True
  324. return False