orm.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. """
  2. Tools for generating forms based on Django models.
  3. """
  4. from wtforms import fields as f
  5. from wtforms import Form
  6. from wtforms import validators
  7. from wtforms.compat import iteritems
  8. from wtforms.ext.django.fields import ModelSelectField
  9. __all__ = (
  10. 'model_fields', 'model_form',
  11. )
  12. class ModelConverterBase(object):
  13. def __init__(self, converters):
  14. self.converters = converters
  15. def convert(self, model, field, field_args):
  16. kwargs = {
  17. 'label': field.verbose_name,
  18. 'description': field.help_text,
  19. 'validators': [],
  20. 'filters': [],
  21. 'default': field.default,
  22. }
  23. if field_args:
  24. kwargs.update(field_args)
  25. if field.blank:
  26. kwargs['validators'].append(validators.Optional())
  27. if field.max_length is not None and field.max_length > 0:
  28. kwargs['validators'].append(validators.Length(max=field.max_length))
  29. ftype = type(field).__name__
  30. if field.choices:
  31. kwargs['choices'] = field.choices
  32. return f.SelectField(**kwargs)
  33. elif ftype in self.converters:
  34. return self.converters[ftype](model, field, kwargs)
  35. else:
  36. converter = getattr(self, 'conv_%s' % ftype, None)
  37. if converter is not None:
  38. return converter(model, field, kwargs)
  39. class ModelConverter(ModelConverterBase):
  40. DEFAULT_SIMPLE_CONVERSIONS = {
  41. f.IntegerField: ['AutoField', 'IntegerField', 'SmallIntegerField', 'PositiveIntegerField', 'PositiveSmallIntegerField'],
  42. f.DecimalField: ['DecimalField', 'FloatField'],
  43. f.FileField: ['FileField', 'FilePathField', 'ImageField'],
  44. f.DateTimeField: ['DateTimeField'],
  45. f.DateField: ['DateField'],
  46. f.BooleanField: ['BooleanField'],
  47. f.TextField: ['CharField', 'PhoneNumberField', 'SlugField'],
  48. f.TextAreaField: ['TextField', 'XMLField'],
  49. }
  50. def __init__(self, extra_converters=None, simple_conversions=None):
  51. converters = {}
  52. if simple_conversions is None:
  53. simple_conversions = self.DEFAULT_SIMPLE_CONVERSIONS
  54. for field_type, django_fields in iteritems(simple_conversions):
  55. converter = self.make_simple_converter(field_type)
  56. for name in django_fields:
  57. converters[name] = converter
  58. if extra_converters:
  59. converters.update(extra_converters)
  60. super(ModelConverter, self).__init__(converters)
  61. def make_simple_converter(self, field_type):
  62. def _converter(model, field, kwargs):
  63. return field_type(**kwargs)
  64. return _converter
  65. def conv_ForeignKey(self, model, field, kwargs):
  66. return ModelSelectField(model=field.rel.to, **kwargs)
  67. def conv_TimeField(self, model, field, kwargs):
  68. def time_only(obj):
  69. try:
  70. return obj.time()
  71. except AttributeError:
  72. return obj
  73. kwargs['filters'].append(time_only)
  74. return f.DateTimeField(format='%H:%M:%S', **kwargs)
  75. def conv_EmailField(self, model, field, kwargs):
  76. kwargs['validators'].append(validators.email())
  77. return f.TextField(**kwargs)
  78. def conv_IPAddressField(self, model, field, kwargs):
  79. kwargs['validators'].append(validators.ip_address())
  80. return f.TextField(**kwargs)
  81. def conv_URLField(self, model, field, kwargs):
  82. kwargs['validators'].append(validators.url())
  83. return f.TextField(**kwargs)
  84. def conv_NullBooleanField(self, model, field, kwargs):
  85. from django.db.models.fields import NOT_PROVIDED
  86. def coerce_nullbool(value):
  87. d = {'None': None, None: None, 'True': True, 'False': False}
  88. if isinstance(value, NOT_PROVIDED):
  89. return None
  90. elif value in d:
  91. return d[value]
  92. else:
  93. return bool(int(value))
  94. choices = ((None, 'Unknown'), (True, 'Yes'), (False, 'No'))
  95. return f.SelectField(choices=choices, coerce=coerce_nullbool, **kwargs)
  96. def model_fields(model, only=None, exclude=None, field_args=None, converter=None):
  97. """
  98. Generate a dictionary of fields for a given Django model.
  99. See `model_form` docstring for description of parameters.
  100. """
  101. converter = converter or ModelConverter()
  102. field_args = field_args or {}
  103. model_fields = ((f.attname, f) for f in model._meta.fields)
  104. if only:
  105. model_fields = (x for x in model_fields if x[0] in only)
  106. elif exclude:
  107. model_fields = (x for x in model_fields if x[0] not in exclude)
  108. field_dict = {}
  109. for name, model_field in model_fields:
  110. field = converter.convert(model, model_field, field_args.get(name))
  111. if field is not None:
  112. field_dict[name] = field
  113. return field_dict
  114. def model_form(model, base_class=Form, only=None, exclude=None, field_args=None, converter=None):
  115. """
  116. Create a wtforms Form for a given Django model class::
  117. from wtforms.ext.django.orm import model_form
  118. from myproject.myapp.models import User
  119. UserForm = model_form(User)
  120. :param model:
  121. A Django ORM model class
  122. :param base_class:
  123. Base form class to extend from. Must be a ``wtforms.Form`` subclass.
  124. :param only:
  125. An optional iterable with the property names that should be included in
  126. the form. Only these properties will have fields.
  127. :param exclude:
  128. An optional iterable with the property names that should be excluded
  129. from the form. All other properties will have fields.
  130. :param field_args:
  131. An optional dictionary of field names mapping to keyword arguments used
  132. to construct each field object.
  133. :param converter:
  134. A converter to generate the fields based on the model properties. If
  135. not set, ``ModelConverter`` is used.
  136. """
  137. field_dict = model_fields(model, only, exclude, field_args, converter)
  138. return type(model._meta.object_name + 'Form', (base_class, ), field_dict)