PageRenderTime 66ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/site/newsite/django_1_0/django/forms/models.py

https://github.com/raminel/geraldo
Python | 608 lines | 580 code | 9 blank | 19 comment | 1 complexity | 25527419451b4583e148cf5f100a4816 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 util import ValidationError, ErrorList
  10. from forms import BaseForm, get_declared_fields
  11. from fields import Field, ChoiceField, IntegerField, EMPTY_VALUES
  12. from widgets import Select, SelectMultiple, HiddenInput, MultipleHiddenInput
  13. from widgets import media_property
  14. from formsets import BaseFormSet, formset_factory, DELETION_FIELD_NAME
  15. __all__ = (
  16. 'ModelForm', 'BaseModelForm', 'model_to_dict', 'fields_for_model',
  17. 'save_instance', 'form_for_model', 'form_for_instance', 'form_for_fields',
  18. 'ModelChoiceField', 'ModelMultipleChoiceField',
  19. )
  20. def save_instance(form, instance, fields=None, fail_message='saved',
  21. commit=True):
  22. """
  23. Saves bound Form ``form``'s cleaned_data into model instance ``instance``.
  24. If commit=True, then the changes to ``instance`` will be saved to the
  25. database. Returns ``instance``.
  26. """
  27. from django.db import models
  28. opts = instance._meta
  29. if form.errors:
  30. raise ValueError("The %s could not be %s because the data didn't"
  31. " validate." % (opts.object_name, fail_message))
  32. cleaned_data = form.cleaned_data
  33. for f in opts.fields:
  34. if not f.editable or isinstance(f, models.AutoField) \
  35. or not f.name in cleaned_data:
  36. continue
  37. if fields and f.name not in fields:
  38. continue
  39. f.save_form_data(instance, cleaned_data[f.name])
  40. # Wrap up the saving of m2m data as a function.
  41. def save_m2m():
  42. opts = instance._meta
  43. cleaned_data = form.cleaned_data
  44. for f in opts.many_to_many:
  45. if fields and f.name not in fields:
  46. continue
  47. if f.name in cleaned_data:
  48. f.save_form_data(instance, cleaned_data[f.name])
  49. if commit:
  50. # If we are committing, save the instance and the m2m data immediately.
  51. instance.save()
  52. save_m2m()
  53. else:
  54. # We're not committing. Add a method to the form to allow deferred
  55. # saving of m2m data.
  56. form.save_m2m = save_m2m
  57. return instance
  58. def make_model_save(model, fields, fail_message):
  59. """Returns the save() method for a Form."""
  60. def save(self, commit=True):
  61. return save_instance(self, model(), fields, fail_message, commit)
  62. return save
  63. def make_instance_save(instance, fields, fail_message):
  64. """Returns the save() method for a Form."""
  65. def save(self, commit=True):
  66. return save_instance(self, instance, fields, fail_message, commit)
  67. return save
  68. def form_for_model(model, form=BaseForm, fields=None,
  69. formfield_callback=lambda f: f.formfield()):
  70. """
  71. Returns a Form class for the given Django model class.
  72. Provide ``form`` if you want to use a custom BaseForm subclass.
  73. Provide ``formfield_callback`` if you want to define different logic for
  74. determining the formfield for a given database field. It's a callable that
  75. takes a database Field instance and returns a form Field instance.
  76. """
  77. warn("form_for_model is deprecated. Use ModelForm instead.",
  78. PendingDeprecationWarning, stacklevel=3)
  79. opts = model._meta
  80. field_list = []
  81. for f in opts.fields + opts.many_to_many:
  82. if not f.editable:
  83. continue
  84. if fields and not f.name in fields:
  85. continue
  86. formfield = formfield_callback(f)
  87. if formfield:
  88. field_list.append((f.name, formfield))
  89. base_fields = SortedDict(field_list)
  90. return type(opts.object_name + 'Form', (form,),
  91. {'base_fields': base_fields, '_model': model,
  92. 'save': make_model_save(model, fields, 'created')})
  93. def form_for_instance(instance, form=BaseForm, fields=None,
  94. formfield_callback=lambda f, **kwargs: f.formfield(**kwargs)):
  95. """
  96. Returns a Form class for the given Django model instance.
  97. Provide ``form`` if you want to use a custom BaseForm subclass.
  98. Provide ``formfield_callback`` if you want to define different logic for
  99. determining the formfield for a given database field. It's a callable that
  100. takes a database Field instance, plus **kwargs, and returns a form Field
  101. instance with the given kwargs (i.e. 'initial').
  102. """
  103. warn("form_for_instance is deprecated. Use ModelForm instead.",
  104. PendingDeprecationWarning, stacklevel=3)
  105. model = instance.__class__
  106. opts = model._meta
  107. field_list = []
  108. for f in opts.fields + opts.many_to_many:
  109. if not f.editable:
  110. continue
  111. if fields and not f.name in fields:
  112. continue
  113. current_value = f.value_from_object(instance)
  114. formfield = formfield_callback(f, initial=current_value)
  115. if formfield:
  116. field_list.append((f.name, formfield))
  117. base_fields = SortedDict(field_list)
  118. return type(opts.object_name + 'InstanceForm', (form,),
  119. {'base_fields': base_fields, '_model': model,
  120. 'save': make_instance_save(instance, fields, 'changed')})
  121. def form_for_fields(field_list):
  122. """
  123. Returns a Form class for the given list of Django database field instances.
  124. """
  125. fields = SortedDict([(f.name, f.formfield())
  126. for f in field_list if f.editable])
  127. return type('FormForFields', (BaseForm,), {'base_fields': fields})
  128. # ModelForms #################################################################
  129. def model_to_dict(instance, fields=None, exclude=None):
  130. """
  131. Returns a dict containing the data in ``instance`` suitable for passing as
  132. a Form's ``initial`` keyword argument.
  133. ``fields`` is an optional list of field names. If provided, only the named
  134. fields will be included in the returned dict.
  135. ``exclude`` is an optional list of field names. If provided, the named
  136. fields will be excluded from the returned dict, even if they are listed in
  137. the ``fields`` argument.
  138. """
  139. # avoid a circular import
  140. from django.db.models.fields.related import ManyToManyField
  141. opts = instance._meta
  142. data = {}
  143. for f in opts.fields + opts.many_to_many:
  144. if not f.editable:
  145. continue
  146. if fields and not f.name in fields:
  147. continue
  148. if exclude and f.name in exclude:
  149. continue
  150. if isinstance(f, ManyToManyField):
  151. # If the object doesn't have a primry key yet, just use an empty
  152. # list for its m2m fields. Calling f.value_from_object will raise
  153. # an exception.
  154. if instance.pk is None:
  155. data[f.name] = []
  156. else:
  157. # MultipleChoiceWidget needs a list of pks, not object instances.
  158. data[f.name] = [obj.pk for obj in f.value_from_object(instance)]
  159. else:
  160. data[f.name] = f.value_from_object(instance)
  161. return data
  162. def fields_for_model(model, fields=None, exclude=None, formfield_callback=lambda f: f.formfield()):
  163. """
  164. Returns a ``SortedDict`` containing form fields for the given model.
  165. ``fields`` is an optional list of field names. If provided, only the named
  166. fields will be included in the returned fields.
  167. ``exclude`` is an optional list of field names. If provided, the named
  168. fields will be excluded from the returned fields, even if they are listed
  169. in the ``fields`` argument.
  170. """
  171. # TODO: if fields is provided, it would be nice to return fields in that order
  172. field_list = []
  173. opts = model._meta
  174. for f in opts.fields + opts.many_to_many:
  175. if not f.editable:
  176. continue
  177. if fields and not f.name in fields:
  178. continue
  179. if exclude and f.name in exclude:
  180. continue
  181. formfield = formfield_callback(f)
  182. if formfield:
  183. field_list.append((f.name, formfield))
  184. return SortedDict(field_list)
  185. class ModelFormOptions(object):
  186. def __init__(self, options=None):
  187. self.model = getattr(options, 'model', None)
  188. self.fields = getattr(options, 'fields', None)
  189. self.exclude = getattr(options, 'exclude', None)
  190. class ModelFormMetaclass(type):
  191. def __new__(cls, name, bases, attrs):
  192. formfield_callback = attrs.pop('formfield_callback',
  193. lambda f: f.formfield())
  194. try:
  195. parents = [b for b in bases if issubclass(b, ModelForm)]
  196. except NameError:
  197. # We are defining ModelForm itself.
  198. parents = None
  199. new_class = super(ModelFormMetaclass, cls).__new__(cls, name, bases,
  200. attrs)
  201. if not parents:
  202. return new_class
  203. if 'media' not in attrs:
  204. new_class.media = media_property(new_class)
  205. declared_fields = get_declared_fields(bases, attrs, False)
  206. opts = new_class._meta = ModelFormOptions(getattr(new_class, 'Meta', None))
  207. if opts.model:
  208. # If a model is defined, extract form fields from it.
  209. fields = fields_for_model(opts.model, opts.fields,
  210. opts.exclude, formfield_callback)
  211. # Override default model fields with any custom declared ones
  212. # (plus, include all the other declared fields).
  213. fields.update(declared_fields)
  214. else:
  215. fields = declared_fields
  216. new_class.declared_fields = declared_fields
  217. new_class.base_fields = fields
  218. return new_class
  219. class BaseModelForm(BaseForm):
  220. def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
  221. initial=None, error_class=ErrorList, label_suffix=':',
  222. empty_permitted=False, instance=None):
  223. opts = self._meta
  224. if instance is None:
  225. # if we didn't get an instance, instantiate a new one
  226. self.instance = opts.model()
  227. object_data = {}
  228. else:
  229. self.instance = instance
  230. object_data = model_to_dict(instance, opts.fields, opts.exclude)
  231. # if initial was provided, it should override the values from instance
  232. if initial is not None:
  233. object_data.update(initial)
  234. BaseForm.__init__(self, data, files, auto_id, prefix, object_data,
  235. error_class, label_suffix, empty_permitted)
  236. def save(self, commit=True):
  237. """
  238. Saves this ``form``'s cleaned_data into model instance
  239. ``self.instance``.
  240. If commit=True, then the changes to ``instance`` will be saved to the
  241. database. Returns ``instance``.
  242. """
  243. if self.instance.pk is None:
  244. fail_message = 'created'
  245. else:
  246. fail_message = 'changed'
  247. return save_instance(self, self.instance, self._meta.fields, fail_message, commit)
  248. class ModelForm(BaseModelForm):
  249. __metaclass__ = ModelFormMetaclass
  250. def modelform_factory(model, form=ModelForm, fields=None, exclude=None,
  251. formfield_callback=lambda f: f.formfield()):
  252. # HACK: we should be able to construct a ModelForm without creating
  253. # and passing in a temporary inner class
  254. class Meta:
  255. pass
  256. setattr(Meta, 'model', model)
  257. setattr(Meta, 'fields', fields)
  258. setattr(Meta, 'exclude', exclude)
  259. class_name = model.__name__ + 'Form'
  260. return ModelFormMetaclass(class_name, (form,), {'Meta': Meta,
  261. 'formfield_callback': formfield_callback})
  262. # ModelFormSets ##############################################################
  263. class BaseModelFormSet(BaseFormSet):
  264. """
  265. A ``FormSet`` for editing a queryset and/or adding new objects to it.
  266. """
  267. model = None
  268. def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
  269. queryset=None, **kwargs):
  270. self.queryset = queryset
  271. defaults = {'data': data, 'files': files, 'auto_id': auto_id, 'prefix': prefix}
  272. if self.max_num > 0:
  273. qs = self.get_queryset()[:self.max_num]
  274. else:
  275. qs = self.get_queryset()
  276. defaults['initial'] = [model_to_dict(obj) for obj in qs]
  277. defaults.update(kwargs)
  278. super(BaseModelFormSet, self).__init__(**defaults)
  279. def get_queryset(self):
  280. if self.queryset is not None:
  281. return self.queryset
  282. return self.model._default_manager.get_query_set()
  283. def save_new(self, form, commit=True):
  284. """Saves and returns a new model instance for the given form."""
  285. return save_instance(form, self.model(), commit=commit)
  286. def save_existing(self, form, instance, commit=True):
  287. """Saves and returns an existing model instance for the given form."""
  288. return save_instance(form, instance, commit=commit)
  289. def save(self, commit=True):
  290. """Saves model instances for every form, adding and changing instances
  291. as necessary, and returns the list of instances.
  292. """
  293. if not commit:
  294. self.saved_forms = []
  295. def save_m2m():
  296. for form in self.saved_forms:
  297. form.save_m2m()
  298. self.save_m2m = save_m2m
  299. return self.save_existing_objects(commit) + self.save_new_objects(commit)
  300. def save_existing_objects(self, commit=True):
  301. self.changed_objects = []
  302. self.deleted_objects = []
  303. if not self.get_queryset():
  304. return []
  305. # Put the objects from self.get_queryset into a dict so they are easy to lookup by pk
  306. existing_objects = {}
  307. for obj in self.get_queryset():
  308. existing_objects[obj.pk] = obj
  309. saved_instances = []
  310. for form in self.initial_forms:
  311. obj = existing_objects[form.cleaned_data[self.model._meta.pk.attname]]
  312. if self.can_delete and form.cleaned_data[DELETION_FIELD_NAME]:
  313. self.deleted_objects.append(obj)
  314. obj.delete()
  315. else:
  316. if form.changed_data:
  317. self.changed_objects.append((obj, form.changed_data))
  318. saved_instances.append(self.save_existing(form, obj, commit=commit))
  319. if not commit:
  320. self.saved_forms.append(form)
  321. return saved_instances
  322. def save_new_objects(self, commit=True):
  323. self.new_objects = []
  324. for form in self.extra_forms:
  325. if not form.has_changed():
  326. continue
  327. # If someone has marked an add form for deletion, don't save the
  328. # object.
  329. if self.can_delete and form.cleaned_data[DELETION_FIELD_NAME]:
  330. continue
  331. self.new_objects.append(self.save_new(form, commit=commit))
  332. if not commit:
  333. self.saved_forms.append(form)
  334. return self.new_objects
  335. def add_fields(self, form, index):
  336. """Add a hidden field for the object's primary key."""
  337. self._pk_field_name = self.model._meta.pk.attname
  338. form.fields[self._pk_field_name] = IntegerField(required=False, widget=HiddenInput)
  339. super(BaseModelFormSet, self).add_fields(form, index)
  340. def modelformset_factory(model, form=ModelForm, formfield_callback=lambda f: f.formfield(),
  341. formset=BaseModelFormSet,
  342. extra=1, can_delete=False, can_order=False,
  343. max_num=0, fields=None, exclude=None):
  344. """
  345. Returns a FormSet class for the given Django model class.
  346. """
  347. form = modelform_factory(model, form=form, fields=fields, exclude=exclude,
  348. formfield_callback=formfield_callback)
  349. FormSet = formset_factory(form, formset, extra=extra, max_num=max_num,
  350. can_order=can_order, can_delete=can_delete)
  351. FormSet.model = model
  352. return FormSet
  353. # InlineFormSets #############################################################
  354. class BaseInlineFormset(BaseModelFormSet):
  355. """A formset for child objects related to a parent."""
  356. def __init__(self, data=None, files=None, instance=None,
  357. save_as_new=False, prefix=None):
  358. from django.db.models.fields.related import RelatedObject
  359. self.instance = instance
  360. self.save_as_new = save_as_new
  361. # is there a better way to get the object descriptor?
  362. self.rel_name = RelatedObject(self.fk.rel.to, self.model, self.fk).get_accessor_name()
  363. super(BaseInlineFormset, self).__init__(data, files, prefix=prefix or self.rel_name)
  364. def _construct_forms(self):
  365. if self.save_as_new:
  366. self._total_form_count = self._initial_form_count
  367. self._initial_form_count = 0
  368. super(BaseInlineFormset, self)._construct_forms()
  369. def get_queryset(self):
  370. """
  371. Returns this FormSet's queryset, but restricted to children of
  372. self.instance
  373. """
  374. kwargs = {self.fk.name: self.instance}
  375. return self.model._default_manager.filter(**kwargs)
  376. def save_new(self, form, commit=True):
  377. kwargs = {self.fk.get_attname(): self.instance.pk}
  378. new_obj = self.model(**kwargs)
  379. return save_instance(form, new_obj, commit=commit)
  380. def _get_foreign_key(parent_model, model, fk_name=None):
  381. """
  382. Finds and returns the ForeignKey from model to parent if there is one.
  383. If fk_name is provided, assume it is the name of the ForeignKey field.
  384. """
  385. # avoid circular import
  386. from django.db.models import ForeignKey
  387. opts = model._meta
  388. if fk_name:
  389. fks_to_parent = [f for f in opts.fields if f.name == fk_name]
  390. if len(fks_to_parent) == 1:
  391. fk = fks_to_parent[0]
  392. if not isinstance(fk, ForeignKey) or fk.rel.to != parent_model:
  393. raise Exception("fk_name '%s' is not a ForeignKey to %s" % (fk_name, parent_model))
  394. elif len(fks_to_parent) == 0:
  395. raise Exception("%s has no field named '%s'" % (model, fk_name))
  396. else:
  397. # Try to discover what the ForeignKey from model to parent_model is
  398. fks_to_parent = [f for f in opts.fields if isinstance(f, ForeignKey) and f.rel.to == parent_model]
  399. if len(fks_to_parent) == 1:
  400. fk = fks_to_parent[0]
  401. elif len(fks_to_parent) == 0:
  402. raise Exception("%s has no ForeignKey to %s" % (model, parent_model))
  403. else:
  404. raise Exception("%s has more than 1 ForeignKey to %s" % (model, parent_model))
  405. return fk
  406. def inlineformset_factory(parent_model, model, form=ModelForm,
  407. formset=BaseInlineFormset, fk_name=None,
  408. fields=None, exclude=None,
  409. extra=3, can_order=False, can_delete=True, max_num=0,
  410. formfield_callback=lambda f: f.formfield()):
  411. """
  412. Returns an ``InlineFormset`` for the given kwargs.
  413. You must provide ``fk_name`` if ``model`` has more than one ``ForeignKey``
  414. to ``parent_model``.
  415. """
  416. fk = _get_foreign_key(parent_model, model, fk_name=fk_name)
  417. # let the formset handle object deletion by default
  418. if exclude is not None:
  419. exclude.append(fk.name)
  420. else:
  421. exclude = [fk.name]
  422. FormSet = modelformset_factory(model, form=form,
  423. formfield_callback=formfield_callback,
  424. formset=formset,
  425. extra=extra, can_delete=can_delete, can_order=can_order,
  426. fields=fields, exclude=exclude, max_num=max_num)
  427. FormSet.fk = fk
  428. return FormSet
  429. # Fields #####################################################################
  430. class ModelChoiceIterator(object):
  431. def __init__(self, field):
  432. self.field = field
  433. self.queryset = field.queryset
  434. def __iter__(self):
  435. if self.field.empty_label is not None:
  436. yield (u"", self.field.empty_label)
  437. if self.field.cache_choices:
  438. if self.field.choice_cache is None:
  439. self.field.choice_cache = [
  440. (obj.pk, self.field.label_from_instance(obj))
  441. for obj in self.queryset.all()
  442. ]
  443. for choice in self.field.choice_cache:
  444. yield choice
  445. else:
  446. for obj in self.queryset.all():
  447. yield (obj.pk, self.field.label_from_instance(obj))
  448. class ModelChoiceField(ChoiceField):
  449. """A ChoiceField whose choices are a model QuerySet."""
  450. # This class is a subclass of ChoiceField for purity, but it doesn't
  451. # actually use any of ChoiceField's implementation.
  452. default_error_messages = {
  453. 'invalid_choice': _(u'Select a valid choice. That choice is not one of'
  454. u' the available choices.'),
  455. }
  456. def __init__(self, queryset, empty_label=u"---------", cache_choices=False,
  457. required=True, widget=Select, label=None, initial=None,
  458. help_text=None, *args, **kwargs):
  459. self.empty_label = empty_label
  460. self.cache_choices = cache_choices
  461. # Call Field instead of ChoiceField __init__() because we don't need
  462. # ChoiceField.__init__().
  463. Field.__init__(self, required, widget, label, initial, help_text,
  464. *args, **kwargs)
  465. self.queryset = queryset
  466. self.choice_cache = None
  467. def _get_queryset(self):
  468. return self._queryset
  469. def _set_queryset(self, queryset):
  470. self._queryset = queryset
  471. self.widget.choices = self.choices
  472. queryset = property(_get_queryset, _set_queryset)
  473. # this method will be used to create object labels by the QuerySetIterator.
  474. # Override it to customize the label.
  475. def label_from_instance(self, obj):
  476. """
  477. This method is used to convert objects into strings; it's used to
  478. generate the labels for the choices presented by this object. Subclasses
  479. can override this method to customize the display of the choices.
  480. """
  481. return smart_unicode(obj)
  482. def _get_choices(self):
  483. # If self._choices is set, then somebody must have manually set
  484. # the property self.choices. In this case, just return self._choices.
  485. if hasattr(self, '_choices'):
  486. return self._choices
  487. # Otherwise, execute the QuerySet in self.queryset to determine the
  488. # choices dynamically. Return a fresh QuerySetIterator that has not been
  489. # consumed. Note that we're instantiating a new QuerySetIterator *each*
  490. # time _get_choices() is called (and, thus, each time self.choices is
  491. # accessed) so that we can ensure the QuerySet has not been consumed. This
  492. # construct might look complicated but it allows for lazy evaluation of
  493. # the queryset.
  494. return ModelChoiceIterator(self)
  495. choices = property(_get_choices, ChoiceField._set_choices)
  496. def clean(self, value):
  497. Field.clean(self, value)
  498. if value in EMPTY_VALUES:
  499. return None
  500. try:
  501. value = self.queryset.get(pk=value)
  502. except self.queryset.model.DoesNotExist:
  503. raise ValidationError(self.error_messages['invalid_choice'])
  504. return value
  505. class ModelMultipleChoiceField(ModelChoiceField):
  506. """A MultipleChoiceField whose choices are a model QuerySet."""
  507. hidden_widget = MultipleHiddenInput
  508. default_error_messages = {
  509. 'list': _(u'Enter a list of values.'),
  510. 'invalid_choice': _(u'Select a valid choice. %s is not one of the'
  511. u' available choices.'),
  512. }
  513. def __init__(self, queryset, cache_choices=False, required=True,
  514. widget=SelectMultiple, label=None, initial=None,
  515. help_text=None, *args, **kwargs):
  516. super(ModelMultipleChoiceField, self).__init__(queryset, None,
  517. cache_choices, required, widget, label, initial, help_text,
  518. *args, **kwargs)
  519. def clean(self, value):
  520. if self.required and not value:
  521. raise ValidationError(self.error_messages['required'])
  522. elif not self.required and not value:
  523. return []
  524. if not isinstance(value, (list, tuple)):
  525. raise ValidationError(self.error_messages['list'])
  526. final_values = []
  527. for val in value:
  528. try:
  529. obj = self.queryset.get(pk=val)
  530. except self.queryset.model.DoesNotExist:
  531. raise ValidationError(self.error_messages['invalid_choice'] % val)
  532. else:
  533. final_values.append(obj)
  534. return final_values