fieldgenerator.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  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 collections
  7. from django import forms
  8. from django.core.validators import EMPTY_VALUES
  9. try:
  10. from django.utils.encoding import smart_text as smart_unicode
  11. except ImportError:
  12. try:
  13. from django.utils.encoding import smart_unicode
  14. except ImportError:
  15. from django.forms.util import smart_unicode
  16. from django.utils.text import capfirst
  17. from mongoengine import (ReferenceField as MongoReferenceField,
  18. EmbeddedDocumentField as MongoEmbeddedDocumentField,
  19. ListField as MongoListField,
  20. MapField as MongoMapField)
  21. from mongodbforms.fields import (MongoCharField, MongoEmailField,
  22. MongoURLField, ReferenceField,
  23. DocumentMultipleChoiceField, ListField,
  24. MapField)
  25. from mongodbforms.widgets import Html5SplitDateTimeWidget
  26. from mongodbforms.documentoptions import create_verbose_name
  27. BLANK_CHOICE_DASH = [("", "---------")]
  28. class MongoFormFieldGenerator(object):
  29. """This class generates Django form-fields for mongoengine-fields."""
  30. # used for fields that fit in one of the generate functions
  31. # but don't actually have the name.
  32. generator_map = {
  33. 'sortedlistfield': 'generate_listfield',
  34. 'longfield': 'generate_intfield',
  35. }
  36. form_field_map = {
  37. 'stringfield': MongoCharField,
  38. 'stringfield_choices': forms.TypedChoiceField,
  39. 'stringfield_long': MongoCharField,
  40. 'emailfield': MongoEmailField,
  41. 'urlfield': MongoURLField,
  42. 'intfield': forms.IntegerField,
  43. 'intfield_choices': forms.TypedChoiceField,
  44. 'floatfield': forms.FloatField,
  45. 'decimalfield': forms.DecimalField,
  46. 'booleanfield': forms.BooleanField,
  47. 'booleanfield_choices': forms.TypedChoiceField,
  48. 'datetimefield': forms.SplitDateTimeField,
  49. 'referencefield': ReferenceField,
  50. 'listfield': ListField,
  51. 'listfield_choices': forms.MultipleChoiceField,
  52. 'listfield_references': DocumentMultipleChoiceField,
  53. 'mapfield': MapField,
  54. 'filefield': forms.FileField,
  55. 'imagefield': forms.ImageField,
  56. }
  57. # uses the same keys as form_field_map
  58. widget_override_map = {
  59. 'stringfield_long': forms.Textarea,
  60. }
  61. def __init__(self, field_overrides={}, widget_overrides={}):
  62. self.form_field_map.update(field_overrides)
  63. self.widget_override_map.update(widget_overrides)
  64. def generate(self, field, **kwargs):
  65. """Tries to lookup a matching formfield generator (lowercase
  66. field-classname) and raises a NotImplementedError of no generator
  67. can be found.
  68. """
  69. # do not handle embedded documents here. They are more or less special
  70. # and require some form of inline formset or something more complex
  71. # to handle then a simple field
  72. if isinstance(field, MongoEmbeddedDocumentField):
  73. return
  74. attr_name = 'generate_%s' % field.__class__.__name__.lower()
  75. if hasattr(self, attr_name):
  76. return getattr(self, attr_name)(field, **kwargs)
  77. for cls in field.__class__.__bases__:
  78. cls_name = cls.__name__.lower()
  79. attr_name = 'generate_%s' % cls_name
  80. if hasattr(self, attr_name):
  81. return getattr(self, attr_name)(field, **kwargs)
  82. if cls_name in self.form_field_map:
  83. attr = self.generator_map.get(cls_name)
  84. return getattr(self, attr)(field, **kwargs)
  85. raise NotImplementedError('%s is not supported by MongoForm' %
  86. field.__class__.__name__)
  87. def get_field_choices(self, field, include_blank=True,
  88. blank_choice=BLANK_CHOICE_DASH):
  89. first_choice = include_blank and blank_choice or []
  90. return first_choice + list(field.choices)
  91. def string_field(self, value):
  92. if value in EMPTY_VALUES:
  93. return None
  94. return smart_unicode(value)
  95. def integer_field(self, value):
  96. if value in EMPTY_VALUES:
  97. return None
  98. return int(value)
  99. def boolean_field(self, value):
  100. if value in EMPTY_VALUES:
  101. return None
  102. return value.lower() == 'true'
  103. def get_field_label(self, field):
  104. if field.verbose_name:
  105. return capfirst(field.verbose_name)
  106. if field.name is not None:
  107. return capfirst(create_verbose_name(field.name))
  108. return ''
  109. def get_field_help_text(self, field):
  110. if field.help_text:
  111. return field.help_text
  112. else:
  113. return ''
  114. def get_field_default(self, field):
  115. if isinstance(field, (MongoListField, MongoMapField)):
  116. f = field.field
  117. else:
  118. f = field
  119. d = {}
  120. if isinstance(f.default, collections.Callable):
  121. d['initial'] = field.default()
  122. d['show_hidden_initial'] = True
  123. return f.default()
  124. else:
  125. d['initial'] = field.default
  126. return f.default
  127. def check_widget(self, map_key):
  128. if map_key in self.widget_override_map:
  129. return {'widget': self.widget_override_map.get(map_key)}
  130. else:
  131. return {}
  132. def generate_stringfield(self, field, **kwargs):
  133. defaults = {
  134. 'label': self.get_field_label(field),
  135. 'initial': self.get_field_default(field),
  136. 'required': field.required,
  137. 'help_text': self.get_field_help_text(field),
  138. }
  139. if field.choices:
  140. map_key = 'stringfield_choices'
  141. defaults.update({
  142. 'choices': self.get_field_choices(field),
  143. 'coerce': self.string_field,
  144. })
  145. elif field.max_length is None:
  146. map_key = 'stringfield_long'
  147. defaults.update({
  148. 'min_length': field.min_length,
  149. })
  150. else:
  151. map_key = 'stringfield'
  152. defaults.update({
  153. 'max_length': field.max_length,
  154. 'min_length': field.min_length,
  155. })
  156. if field.regex:
  157. defaults['regex'] = field.regex
  158. form_class = self.form_field_map.get(map_key)
  159. defaults.update(self.check_widget(map_key))
  160. defaults.update(kwargs)
  161. return form_class(**defaults)
  162. def generate_emailfield(self, field, **kwargs):
  163. map_key = 'emailfield'
  164. defaults = {
  165. 'required': field.required,
  166. 'min_length': field.min_length,
  167. 'max_length': field.max_length,
  168. 'initial': self.get_field_default(field),
  169. 'label': self.get_field_label(field),
  170. 'help_text': self.get_field_help_text(field)
  171. }
  172. defaults.update(self.check_widget(map_key))
  173. form_class = self.form_field_map.get(map_key)
  174. defaults.update(kwargs)
  175. return form_class(**defaults)
  176. def generate_urlfield(self, field, **kwargs):
  177. map_key = 'urlfield'
  178. defaults = {
  179. 'required': field.required,
  180. 'min_length': field.min_length,
  181. 'max_length': field.max_length,
  182. 'initial': self.get_field_default(field),
  183. 'label': self.get_field_label(field),
  184. 'help_text': self.get_field_help_text(field)
  185. }
  186. form_class = self.form_field_map.get(map_key)
  187. defaults.update(self.check_widget(map_key))
  188. defaults.update(kwargs)
  189. return form_class(**defaults)
  190. def generate_intfield(self, field, **kwargs):
  191. defaults = {
  192. 'required': field.required,
  193. 'initial': self.get_field_default(field),
  194. 'label': self.get_field_label(field),
  195. 'help_text': self.get_field_help_text(field)
  196. }
  197. if field.choices:
  198. map_key = 'intfield_choices'
  199. defaults.update({
  200. 'coerce': self.integer_field,
  201. 'empty_value': None,
  202. 'choices': self.get_field_choices(field),
  203. })
  204. else:
  205. map_key = 'intfield'
  206. defaults.update({
  207. 'min_value': field.min_value,
  208. 'max_value': field.max_value,
  209. })
  210. form_class = self.form_field_map.get(map_key)
  211. defaults.update(self.check_widget(map_key))
  212. defaults.update(kwargs)
  213. return form_class(**defaults)
  214. def generate_floatfield(self, field, **kwargs):
  215. map_key = 'floatfield'
  216. defaults = {
  217. 'label': self.get_field_label(field),
  218. 'initial': self.get_field_default(field),
  219. 'required': field.required,
  220. 'min_value': field.min_value,
  221. 'max_value': field.max_value,
  222. 'help_text': self.get_field_help_text(field)
  223. }
  224. form_class = self.form_field_map.get(map_key)
  225. defaults.update(self.check_widget(map_key))
  226. defaults.update(kwargs)
  227. return form_class(**defaults)
  228. def generate_decimalfield(self, field, **kwargs):
  229. map_key = 'decimalfield'
  230. defaults = {
  231. 'label': self.get_field_label(field),
  232. 'initial': self.get_field_default(field),
  233. 'required': field.required,
  234. 'min_value': field.min_value,
  235. 'max_value': field.max_value,
  236. 'decimal_places': field.precision,
  237. 'help_text': self.get_field_help_text(field)
  238. }
  239. form_class = self.form_field_map.get(map_key)
  240. defaults.update(self.check_widget(map_key))
  241. defaults.update(kwargs)
  242. return form_class(**defaults)
  243. def generate_booleanfield(self, field, **kwargs):
  244. defaults = {
  245. 'required': field.required,
  246. 'initial': self.get_field_default(field),
  247. 'label': self.get_field_label(field),
  248. 'help_text': self.get_field_help_text(field)
  249. }
  250. if field.choices:
  251. map_key = 'booleanfield_choices'
  252. defaults.update({
  253. 'coerce': self.boolean_field,
  254. 'empty_value': None,
  255. 'choices': self.get_field_choices(field),
  256. })
  257. else:
  258. map_key = 'booleanfield'
  259. form_class = self.form_field_map.get(map_key)
  260. defaults.update(self.check_widget(map_key))
  261. defaults.update(kwargs)
  262. return form_class(**defaults)
  263. def generate_datetimefield(self, field, **kwargs):
  264. map_key = 'datetimefield'
  265. defaults = {
  266. 'required': field.required,
  267. 'initial': self.get_field_default(field),
  268. 'label': self.get_field_label(field),
  269. }
  270. form_class = self.form_field_map.get(map_key)
  271. defaults.update(self.check_widget(map_key))
  272. defaults.update(kwargs)
  273. return form_class(**defaults)
  274. def generate_referencefield(self, field, **kwargs):
  275. map_key = 'referencefield'
  276. defaults = {
  277. 'label': self.get_field_label(field),
  278. 'help_text': self.get_field_help_text(field),
  279. 'required': field.required,
  280. 'queryset': field.document_type.objects.clone(),
  281. }
  282. form_class = self.form_field_map.get(map_key)
  283. defaults.update(self.check_widget(map_key))
  284. defaults.update(kwargs)
  285. return form_class(**defaults)
  286. def generate_listfield(self, field, **kwargs):
  287. # We can't really handle embedded documents here.
  288. # So we just ignore them
  289. if isinstance(field.field, MongoEmbeddedDocumentField):
  290. return
  291. defaults = {
  292. 'label': self.get_field_label(field),
  293. 'help_text': self.get_field_help_text(field),
  294. 'required': field.required,
  295. }
  296. if field.field.choices:
  297. map_key = 'listfield_choices'
  298. defaults.update({
  299. 'choices': field.field.choices,
  300. 'widget': forms.CheckboxSelectMultiple
  301. })
  302. elif isinstance(field.field, MongoReferenceField):
  303. map_key = 'listfield_references'
  304. defaults.update({
  305. 'queryset': field.field.document_type.objects.clone(),
  306. })
  307. else:
  308. map_key = 'listfield'
  309. form_field = self.generate(field.field)
  310. defaults.update({
  311. 'contained_field': form_field.__class__,
  312. })
  313. form_class = self.form_field_map.get(map_key)
  314. defaults.update(self.check_widget(map_key))
  315. defaults.update(kwargs)
  316. return form_class(**defaults)
  317. def generate_mapfield(self, field, **kwargs):
  318. # We can't really handle embedded documents here.
  319. # So we just ignore them
  320. if isinstance(field.field, MongoEmbeddedDocumentField):
  321. return
  322. map_key = 'mapfield'
  323. form_field = self.generate(field.field)
  324. defaults = {
  325. 'label': self.get_field_label(field),
  326. 'help_text': self.get_field_help_text(field),
  327. 'required': field.required,
  328. 'contained_field': form_field.__class__,
  329. }
  330. form_class = self.form_field_map.get(map_key)
  331. defaults.update(self.check_widget(map_key))
  332. defaults.update(kwargs)
  333. return form_class(**defaults)
  334. def generate_filefield(self, field, **kwargs):
  335. map_key = 'filefield'
  336. defaults = {
  337. 'required': field.required,
  338. 'label': self.get_field_label(field),
  339. 'initial': self.get_field_default(field),
  340. 'help_text': self.get_field_help_text(field)
  341. }
  342. form_class = self.form_field_map.get(map_key)
  343. defaults.update(self.check_widget(map_key))
  344. defaults.update(kwargs)
  345. return form_class(**defaults)
  346. def generate_imagefield(self, field, **kwargs):
  347. map_key = 'imagefield'
  348. defaults = {
  349. 'required': field.required,
  350. 'label': self.get_field_label(field),
  351. 'initial': self.get_field_default(field),
  352. 'help_text': self.get_field_help_text(field)
  353. }
  354. form_class = self.form_field_map.get(map_key)
  355. defaults.update(self.check_widget(map_key))
  356. defaults.update(kwargs)
  357. return form_class(**defaults)
  358. class MongoDefaultFormFieldGenerator(MongoFormFieldGenerator):
  359. """This class generates Django form-fields for mongoengine-fields."""
  360. def generate(self, field, **kwargs):
  361. """Tries to lookup a matching formfield generator (lowercase
  362. field-classname) and raises a NotImplementedError of no generator
  363. can be found.
  364. """
  365. try:
  366. sup = super(MongoDefaultFormFieldGenerator, self)
  367. return sup.generate(field, **kwargs)
  368. except NotImplementedError:
  369. # a normal charfield is always a good guess
  370. # for a widget.
  371. # TODO: Somehow add a warning
  372. defaults = {'required': field.required}
  373. if hasattr(field, 'min_length'):
  374. defaults['min_length'] = field.min_length
  375. if hasattr(field, 'max_length'):
  376. defaults['max_length'] = field.max_length
  377. if hasattr(field, 'default'):
  378. defaults['initial'] = field.default
  379. defaults.update(kwargs)
  380. return forms.CharField(**defaults)
  381. class Html5FormFieldGenerator(MongoDefaultFormFieldGenerator):
  382. def check_widget(self, map_key):
  383. override = super(Html5FormFieldGenerator, self).check_widget(map_key)
  384. if override != {}:
  385. return override
  386. chunks = map_key.split('field')
  387. kind = chunks[0]
  388. if kind == 'email':
  389. if hasattr(forms, 'EmailInput'):
  390. return {'widget': forms.EmailInput}
  391. else:
  392. input = forms.TextInput
  393. input.input_type = 'email'
  394. return {'widget': input}
  395. elif kind in ['int', 'float'] and len(chunks) < 2:
  396. if hasattr(forms, 'NumberInput'):
  397. return {'widget': forms.NumberInput}
  398. else:
  399. input = forms.TextInput
  400. input.input_type = 'number'
  401. return {'widget': input}
  402. elif kind == 'url':
  403. if hasattr(forms, 'URLInput'):
  404. return {'widget': forms.URLInput}
  405. else:
  406. input = forms.TextInput
  407. input.input_type = 'url'
  408. return {'widget': input}
  409. elif kind == 'datetime':
  410. return {'widget': Html5SplitDateTimeWidget}
  411. else:
  412. return {}