PageRenderTime 26ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/litclub/django/newforms/models.py

https://github.com/andrewkuzmych/litclub
Python | 398 lines | 370 code | 9 blank | 19 comment | 1 complexity | 901ef20acdd170484fcfe3626a50d34c MD5 | raw file
  1. """
  2. Helper functions for creating Form classes from Django models
  3. and database field objects.
  4. """
  5. from warnings import warn
  6. from django.utils.translation import ugettext_lazy as _
  7. from django.utils.encoding import smart_unicode
  8. from django.utils.datastructures import SortedDict
  9. from django.core.exceptions import ImproperlyConfigured
  10. from util import ValidationError, ErrorList
  11. from forms import BaseForm, get_declared_fields
  12. from fields import Field, ChoiceField, EMPTY_VALUES
  13. from widgets import Select, SelectMultiple, MultipleHiddenInput
  14. __all__ = (
  15. 'ModelForm', 'BaseModelForm', 'model_to_dict', 'fields_for_model',
  16. 'save_instance', 'form_for_model', 'form_for_instance', 'form_for_fields',
  17. 'ModelChoiceField', 'ModelMultipleChoiceField'
  18. )
  19. def save_instance(form, instance, fields=None, fail_message='saved',
  20. commit=True):
  21. """
  22. Saves bound Form ``form``'s cleaned_data into model instance ``instance``.
  23. If commit=True, then the changes to ``instance`` will be saved to the
  24. database. Returns ``instance``.
  25. """
  26. from django.db import models
  27. opts = instance.__class__._meta
  28. if form.errors:
  29. raise ValueError("The %s could not be %s because the data didn't"
  30. " validate." % (opts.object_name, fail_message))
  31. cleaned_data = form.cleaned_data
  32. for f in opts.fields:
  33. if not f.editable or isinstance(f, models.AutoField) \
  34. or not f.name in cleaned_data:
  35. continue
  36. if fields and f.name not in fields:
  37. continue
  38. f.save_form_data(instance, cleaned_data[f.name])
  39. # Wrap up the saving of m2m data as a function.
  40. def save_m2m():
  41. opts = instance.__class__._meta
  42. cleaned_data = form.cleaned_data
  43. for f in opts.many_to_many:
  44. if fields and f.name not in fields:
  45. continue
  46. if f.name in cleaned_data:
  47. f.save_form_data(instance, cleaned_data[f.name])
  48. if commit:
  49. # If we are committing, save the instance and the m2m data immediately.
  50. instance.save()
  51. save_m2m()
  52. else:
  53. # We're not committing. Add a method to the form to allow deferred
  54. # saving of m2m data.
  55. form.save_m2m = save_m2m
  56. return instance
  57. def make_model_save(model, fields, fail_message):
  58. """Returns the save() method for a Form."""
  59. def save(self, commit=True):
  60. return save_instance(self, model(), fields, fail_message, commit)
  61. return save
  62. def make_instance_save(instance, fields, fail_message):
  63. """Returns the save() method for a Form."""
  64. def save(self, commit=True):
  65. return save_instance(self, instance, fields, fail_message, commit)
  66. return save
  67. def form_for_model(model, form=BaseForm, fields=None,
  68. formfield_callback=lambda f: f.formfield()):
  69. """
  70. Returns a Form class for the given Django model class.
  71. Provide ``form`` if you want to use a custom BaseForm subclass.
  72. Provide ``formfield_callback`` if you want to define different logic for
  73. determining the formfield for a given database field. It's a callable that
  74. takes a database Field instance and returns a form Field instance.
  75. """
  76. warn("form_for_model is deprecated. Use ModelForm instead.",
  77. PendingDeprecationWarning, stacklevel=3)
  78. opts = model._meta
  79. field_list = []
  80. for f in opts.fields + opts.many_to_many:
  81. if not f.editable:
  82. continue
  83. if fields and not f.name in fields:
  84. continue
  85. formfield = formfield_callback(f)
  86. if formfield:
  87. field_list.append((f.name, formfield))
  88. base_fields = SortedDict(field_list)
  89. return type(opts.object_name + 'Form', (form,),
  90. {'base_fields': base_fields, '_model': model,
  91. 'save': make_model_save(model, fields, 'created')})
  92. def form_for_instance(instance, form=BaseForm, fields=None,
  93. formfield_callback=lambda f, **kwargs: f.formfield(**kwargs)):
  94. """
  95. Returns a Form class for the given Django model instance.
  96. Provide ``form`` if you want to use a custom BaseForm subclass.
  97. Provide ``formfield_callback`` if you want to define different logic for
  98. determining the formfield for a given database field. It's a callable that
  99. takes a database Field instance, plus **kwargs, and returns a form Field
  100. instance with the given kwargs (i.e. 'initial').
  101. """
  102. warn("form_for_instance is deprecated. Use ModelForm instead.",
  103. PendingDeprecationWarning, stacklevel=3)
  104. model = instance.__class__
  105. opts = model._meta
  106. field_list = []
  107. for f in opts.fields + opts.many_to_many:
  108. if not f.editable:
  109. continue
  110. if fields and not f.name in fields:
  111. continue
  112. current_value = f.value_from_object(instance)
  113. formfield = formfield_callback(f, initial=current_value)
  114. if formfield:
  115. field_list.append((f.name, formfield))
  116. base_fields = SortedDict(field_list)
  117. return type(opts.object_name + 'InstanceForm', (form,),
  118. {'base_fields': base_fields, '_model': model,
  119. 'save': make_instance_save(instance, fields, 'changed')})
  120. def form_for_fields(field_list):
  121. """
  122. Returns a Form class for the given list of Django database field instances.
  123. """
  124. fields = SortedDict([(f.name, f.formfield())
  125. for f in field_list if f.editable])
  126. return type('FormForFields', (BaseForm,), {'base_fields': fields})
  127. # ModelForms #################################################################
  128. def model_to_dict(instance, fields=None, exclude=None):
  129. """
  130. Returns a dict containing the data in ``instance`` suitable for passing as
  131. a Form's ``initial`` keyword argument.
  132. ``fields`` is an optional list of field names. If provided, only the named
  133. fields will be included in the returned dict.
  134. ``exclude`` is an optional list of field names. If provided, the named
  135. fields will be excluded from the returned dict, even if they are listed in
  136. the ``fields`` argument.
  137. """
  138. # avoid a circular import
  139. from django.db.models.fields.related import ManyToManyField
  140. opts = instance._meta
  141. data = {}
  142. for f in opts.fields + opts.many_to_many:
  143. if not f.editable:
  144. continue
  145. if fields and not f.name in fields:
  146. continue
  147. if exclude and f.name in exclude:
  148. continue
  149. if isinstance(f, ManyToManyField):
  150. # If the object doesn't have a primry key yet, just use an empty
  151. # list for its m2m fields. Calling f.value_from_object will raise
  152. # an exception.
  153. if instance.pk is None:
  154. data[f.name] = []
  155. else:
  156. # MultipleChoiceWidget needs a list of pks, not object instances.
  157. data[f.name] = [obj.pk for obj in f.value_from_object(instance)]
  158. else:
  159. data[f.name] = f.value_from_object(instance)
  160. return data
  161. def fields_for_model(model, fields=None, exclude=None, formfield_callback=lambda f: f.formfield()):
  162. """
  163. Returns a ``SortedDict`` containing form fields for the given model.
  164. ``fields`` is an optional list of field names. If provided, only the named
  165. fields will be included in the returned fields.
  166. ``exclude`` is an optional list of field names. If provided, the named
  167. fields will be excluded from the returned fields, even if they are listed
  168. in the ``fields`` argument.
  169. """
  170. # TODO: if fields is provided, it would be nice to return fields in that order
  171. field_list = []
  172. opts = model._meta
  173. for f in opts.fields + opts.many_to_many:
  174. if not f.editable:
  175. continue
  176. if fields and not f.name in fields:
  177. continue
  178. if exclude and f.name in exclude:
  179. continue
  180. formfield = formfield_callback(f)
  181. if formfield:
  182. field_list.append((f.name, formfield))
  183. return SortedDict(field_list)
  184. class ModelFormOptions(object):
  185. def __init__(self, options=None):
  186. self.model = getattr(options, 'model', None)
  187. self.fields = getattr(options, 'fields', None)
  188. self.exclude = getattr(options, 'exclude', None)
  189. class ModelFormMetaclass(type):
  190. def __new__(cls, name, bases, attrs,
  191. formfield_callback=lambda f: f.formfield()):
  192. try:
  193. parents = [b for b in bases if issubclass(b, ModelForm)]
  194. except NameError:
  195. # We are defining ModelForm itself.
  196. parents = None
  197. if not parents:
  198. return super(ModelFormMetaclass, cls).__new__(cls, name, bases,
  199. attrs)
  200. new_class = type.__new__(cls, name, bases, attrs)
  201. declared_fields = get_declared_fields(bases, attrs, False)
  202. opts = new_class._meta = ModelFormOptions(getattr(new_class, 'Meta', None))
  203. if opts.model:
  204. # If a model is defined, extract form fields from it.
  205. fields = fields_for_model(opts.model, opts.fields,
  206. opts.exclude, formfield_callback)
  207. # Override default model fields with any custom declared ones
  208. # (plus, include all the other declared fields).
  209. fields.update(declared_fields)
  210. else:
  211. fields = declared_fields
  212. new_class.declared_fields = declared_fields
  213. new_class.base_fields = fields
  214. return new_class
  215. class BaseModelForm(BaseForm):
  216. def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
  217. initial=None, error_class=ErrorList, label_suffix=':',
  218. instance=None):
  219. opts = self._meta
  220. if instance is None:
  221. # if we didn't get an instance, instantiate a new one
  222. self.instance = opts.model()
  223. object_data = {}
  224. else:
  225. self.instance = instance
  226. object_data = model_to_dict(instance, opts.fields, opts.exclude)
  227. # if initial was provided, it should override the values from instance
  228. if initial is not None:
  229. object_data.update(initial)
  230. BaseForm.__init__(self, data, files, auto_id, prefix, object_data, error_class, label_suffix)
  231. def save(self, commit=True):
  232. """
  233. Saves this ``form``'s cleaned_data into model instance
  234. ``self.instance``.
  235. If commit=True, then the changes to ``instance`` will be saved to the
  236. database. Returns ``instance``.
  237. """
  238. if self.instance.pk is None:
  239. fail_message = 'created'
  240. else:
  241. fail_message = 'changed'
  242. return save_instance(self, self.instance, self._meta.fields, fail_message, commit)
  243. class ModelForm(BaseModelForm):
  244. __metaclass__ = ModelFormMetaclass
  245. # Fields #####################################################################
  246. class ModelChoiceIterator(object):
  247. def __init__(self, field):
  248. self.field = field
  249. self.queryset = field.queryset
  250. def __iter__(self):
  251. if self.field.empty_label is not None:
  252. yield (u"", self.field.empty_label)
  253. for obj in self.queryset:
  254. yield (obj.pk, self.field.label_from_instance(obj))
  255. # Clear the QuerySet cache if required.
  256. if not self.field.cache_choices:
  257. self.queryset._result_cache = None
  258. class ModelChoiceField(ChoiceField):
  259. """A ChoiceField whose choices are a model QuerySet."""
  260. # This class is a subclass of ChoiceField for purity, but it doesn't
  261. # actually use any of ChoiceField's implementation.
  262. default_error_messages = {
  263. 'invalid_choice': _(u'Select a valid choice. That choice is not one of'
  264. u' the available choices.'),
  265. }
  266. def __init__(self, queryset, empty_label=u"---------", cache_choices=False,
  267. required=True, widget=Select, label=None, initial=None,
  268. help_text=None, *args, **kwargs):
  269. self.empty_label = empty_label
  270. self.cache_choices = cache_choices
  271. # Call Field instead of ChoiceField __init__() because we don't need
  272. # ChoiceField.__init__().
  273. Field.__init__(self, required, widget, label, initial, help_text,
  274. *args, **kwargs)
  275. self.queryset = queryset
  276. def _get_queryset(self):
  277. return self._queryset
  278. def _set_queryset(self, queryset):
  279. self._queryset = queryset
  280. self.widget.choices = self.choices
  281. queryset = property(_get_queryset, _set_queryset)
  282. # this method will be used to create object labels by the QuerySetIterator.
  283. # Override it to customize the label.
  284. def label_from_instance(self, obj):
  285. """
  286. This method is used to convert objects into strings; it's used to
  287. generate the labels for the choices presented by this object. Subclasses
  288. can override this method to customize the display of the choices.
  289. """
  290. return smart_unicode(obj)
  291. def _get_choices(self):
  292. # If self._choices is set, then somebody must have manually set
  293. # the property self.choices. In this case, just return self._choices.
  294. if hasattr(self, '_choices'):
  295. return self._choices
  296. # Otherwise, execute the QuerySet in self.queryset to determine the
  297. # choices dynamically. Return a fresh QuerySetIterator that has not been
  298. # consumed. Note that we're instantiating a new QuerySetIterator *each*
  299. # time _get_choices() is called (and, thus, each time self.choices is
  300. # accessed) so that we can ensure the QuerySet has not been consumed. This
  301. # construct might look complicated but it allows for lazy evaluation of
  302. # the queryset.
  303. return ModelChoiceIterator(self)
  304. def _set_choices(self, value):
  305. # This method is copied from ChoiceField._set_choices(). It's necessary
  306. # because property() doesn't allow a subclass to overwrite only
  307. # _get_choices without implementing _set_choices.
  308. self._choices = self.widget.choices = list(value)
  309. choices = property(_get_choices, _set_choices)
  310. def clean(self, value):
  311. Field.clean(self, value)
  312. if value in EMPTY_VALUES:
  313. return None
  314. try:
  315. value = self.queryset.get(pk=value)
  316. except self.queryset.model.DoesNotExist:
  317. raise ValidationError(self.error_messages['invalid_choice'])
  318. return value
  319. class ModelMultipleChoiceField(ModelChoiceField):
  320. """A MultipleChoiceField whose choices are a model QuerySet."""
  321. hidden_widget = MultipleHiddenInput
  322. default_error_messages = {
  323. 'list': _(u'Enter a list of values.'),
  324. 'invalid_choice': _(u'Select a valid choice. %s is not one of the'
  325. u' available choices.'),
  326. }
  327. def __init__(self, queryset, cache_choices=False, required=True,
  328. widget=SelectMultiple, label=None, initial=None,
  329. help_text=None, *args, **kwargs):
  330. super(ModelMultipleChoiceField, self).__init__(queryset, None,
  331. cache_choices, required, widget, label, initial, help_text,
  332. *args, **kwargs)
  333. def clean(self, value):
  334. if self.required and not value:
  335. raise ValidationError(self.error_messages['required'])
  336. elif not self.required and not value:
  337. return []
  338. if not isinstance(value, (list, tuple)):
  339. raise ValidationError(self.error_messages['list'])
  340. final_values = []
  341. for val in value:
  342. try:
  343. obj = self.queryset.get(pk=val)
  344. except self.queryset.model.DoesNotExist:
  345. raise ValidationError(self.error_messages['invalid_choice'] % val)
  346. else:
  347. final_values.append(obj)
  348. return final_values