documents.py 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966
  1. import os
  2. import itertools
  3. from collections import Callable, OrderedDict
  4. from functools import reduce
  5. from django.forms.forms import (BaseForm, get_declared_fields,
  6. NON_FIELD_ERRORS, pretty_name)
  7. from django.forms.widgets import media_property
  8. from django.core.exceptions import FieldError
  9. from django.core.validators import EMPTY_VALUES
  10. from django.forms.util import ErrorList
  11. from django.forms.formsets import BaseFormSet, formset_factory
  12. from django.utils.translation import ugettext_lazy as _, ugettext
  13. from django.utils.text import capfirst, get_valid_filename
  14. from mongoengine.fields import (ObjectIdField, ListField, ReferenceField,
  15. FileField, MapField, EmbeddedDocumentField)
  16. try:
  17. from mongoengine.base import ValidationError
  18. except ImportError:
  19. from mongoengine.errors import ValidationError
  20. from mongoengine.queryset import OperationError, Q
  21. from mongoengine.queryset.base import BaseQuerySet
  22. from mongoengine.connection import get_db, DEFAULT_CONNECTION_NAME
  23. from mongoengine.base import NON_FIELD_ERRORS as MONGO_NON_FIELD_ERRORS
  24. from gridfs import GridFS
  25. from mongodbforms.documentoptions import DocumentMetaWrapper
  26. from mongodbforms.util import with_metaclass, load_field_generator
  27. _fieldgenerator = load_field_generator()
  28. def _get_unique_filename(name, db_alias=DEFAULT_CONNECTION_NAME,
  29. collection_name='fs'):
  30. fs = GridFS(get_db(db_alias), collection_name)
  31. file_root, file_ext = os.path.splitext(get_valid_filename(name))
  32. count = itertools.count(1)
  33. while fs.exists(filename=name):
  34. # file_ext includes the dot.
  35. name = os.path.join("%s_%s%s" % (file_root, next(count), file_ext))
  36. return name
  37. def _save_iterator_file(field, instance, uploaded_file, file_data=None):
  38. """
  39. Takes care of saving a file for a list field. Returns a Mongoengine
  40. fileproxy object or the file field.
  41. """
  42. # for a new file we need a new proxy object
  43. if file_data is None:
  44. file_data = field.field.get_proxy_obj(key=field.name,
  45. instance=instance)
  46. if file_data.instance is None:
  47. file_data.instance = instance
  48. if file_data.key is None:
  49. file_data.key = field.name
  50. if file_data.grid_id:
  51. file_data.delete()
  52. uploaded_file.seek(0)
  53. filename = _get_unique_filename(uploaded_file.name, field.field.db_alias,
  54. field.field.collection_name)
  55. file_data.put(uploaded_file, content_type=uploaded_file.content_type,
  56. filename=filename)
  57. file_data.close()
  58. return file_data
  59. def construct_instance(form, instance, fields=None, exclude=None):
  60. """
  61. Constructs and returns a document instance from the bound ``form``'s
  62. ``cleaned_data``, but does not save the returned instance to the
  63. database.
  64. """
  65. cleaned_data = form.cleaned_data
  66. file_field_list = []
  67. # check wether object is instantiated
  68. if isinstance(instance, type):
  69. instance = instance()
  70. for f in instance._fields.values():
  71. if isinstance(f, ObjectIdField):
  72. continue
  73. if not f.name in cleaned_data:
  74. continue
  75. if fields is not None and f.name not in fields:
  76. continue
  77. if exclude and f.name in exclude:
  78. continue
  79. # Defer saving file-type fields until after the other fields, so a
  80. # callable upload_to can use the values from other fields.
  81. if isinstance(f, FileField) or \
  82. (isinstance(f, (MapField, ListField)) and
  83. isinstance(f.field, FileField)):
  84. file_field_list.append(f)
  85. else:
  86. setattr(instance, f.name, cleaned_data.get(f.name))
  87. for f in file_field_list:
  88. if isinstance(f, MapField):
  89. map_field = getattr(instance, f.name)
  90. uploads = cleaned_data[f.name]
  91. for key, uploaded_file in uploads.items():
  92. if uploaded_file is None:
  93. continue
  94. file_data = map_field.get(key, None)
  95. map_field[key] = _save_iterator_file(f, instance,
  96. uploaded_file, file_data)
  97. setattr(instance, f.name, map_field)
  98. elif isinstance(f, ListField):
  99. list_field = getattr(instance, f.name)
  100. uploads = cleaned_data[f.name]
  101. for i, uploaded_file in enumerate(uploads):
  102. if uploaded_file is None:
  103. continue
  104. try:
  105. file_data = list_field[i]
  106. except IndexError:
  107. file_data = None
  108. file_obj = _save_iterator_file(f, instance,
  109. uploaded_file, file_data)
  110. try:
  111. list_field[i] = file_obj
  112. except IndexError:
  113. list_field.append(file_obj)
  114. setattr(instance, f.name, list_field)
  115. else:
  116. field = getattr(instance, f.name)
  117. upload = cleaned_data[f.name]
  118. if upload is None:
  119. continue
  120. try:
  121. upload.file.seek(0)
  122. # delete first to get the names right
  123. if field.grid_id:
  124. field.delete()
  125. filename = _get_unique_filename(upload.name, f.db_alias,
  126. f.collection_name)
  127. field.put(upload, content_type=upload.content_type,
  128. filename=filename)
  129. setattr(instance, f.name, field)
  130. except AttributeError:
  131. # file was already uploaded and not changed during edit.
  132. # upload is already the gridfsproxy object we need.
  133. upload.get()
  134. setattr(instance, f.name, upload)
  135. return instance
  136. def save_instance(form, instance, fields=None, fail_message='saved',
  137. commit=True, exclude=None, construct=True):
  138. """
  139. Saves bound Form ``form``'s cleaned_data into document ``instance``.
  140. If commit=True, then the changes to ``instance`` will be saved to the
  141. database. Returns ``instance``.
  142. If construct=False, assume ``instance`` has already been constructed and
  143. just needs to be saved.
  144. """
  145. if construct:
  146. instance = construct_instance(form, instance, fields, exclude)
  147. if form.errors:
  148. raise ValueError("The %s could not be %s because the data didn't"
  149. " validate." % (instance.__class__.__name__,
  150. fail_message))
  151. if commit and hasattr(instance, 'save'):
  152. # see BaseDocumentForm._post_clean for an explanation
  153. #if len(form._meta._dont_save) > 0:
  154. # data = instance._data
  155. # new_data = dict([(n, f) for n, f in data.items() if not n \
  156. # in form._meta._dont_save])
  157. # instance._data = new_data
  158. # instance.save()
  159. # instance._data = data
  160. #else:
  161. instance.save()
  162. return instance
  163. def document_to_dict(instance, fields=None, exclude=None):
  164. """
  165. Returns a dict containing the data in ``instance`` suitable for passing as
  166. a Form's ``initial`` keyword argument.
  167. ``fields`` is an optional list of field names. If provided, only the named
  168. fields will be included in the returned dict.
  169. ``exclude`` is an optional list of field names. If provided, the named
  170. fields will be excluded from the returned dict, even if they are listed in
  171. the ``fields`` argument.
  172. """
  173. data = {}
  174. for f in instance._fields.values():
  175. if fields and not f.name in fields:
  176. continue
  177. if exclude and f.name in exclude:
  178. continue
  179. data[f.name] = getattr(instance, f.name, '')
  180. return data
  181. def fields_for_document(document, fields=None, exclude=None, widgets=None,
  182. formfield_callback=None,
  183. field_generator=_fieldgenerator):
  184. """
  185. Returns a ``SortedDict`` containing form fields for the given model.
  186. ``fields`` is an optional list of field names. If provided, only the named
  187. fields will be included in the returned fields.
  188. ``exclude`` is an optional list of field names. If provided, the named
  189. fields will be excluded from the returned fields, even if they are listed
  190. in the ``fields`` argument.
  191. """
  192. field_list = []
  193. if isinstance(field_generator, type):
  194. field_generator = field_generator()
  195. if formfield_callback and not isinstance(formfield_callback, Callable):
  196. raise TypeError('formfield_callback must be a function or callable')
  197. for name in document._fields_ordered:
  198. f = document._fields.get(name)
  199. if isinstance(f, ObjectIdField):
  200. continue
  201. if fields and not f.name in fields:
  202. continue
  203. if exclude and f.name in exclude:
  204. continue
  205. if widgets and f.name in widgets:
  206. kwargs = {'widget': widgets[f.name]}
  207. else:
  208. kwargs = {}
  209. if formfield_callback:
  210. formfield = formfield_callback(f, **kwargs)
  211. else:
  212. formfield = field_generator.generate(f, **kwargs)
  213. if formfield:
  214. field_list.append((f.name, formfield))
  215. field_dict = OrderedDict(field_list)
  216. if fields:
  217. field_dict = OrderedDict(
  218. [(f, field_dict.get(f)) for f in fields
  219. if ((not exclude) or (exclude and f not in exclude))]
  220. )
  221. return field_dict
  222. class ModelFormOptions(object):
  223. def __init__(self, options=None):
  224. # document class can be declared with 'document =' or 'model ='
  225. self.document = getattr(options, 'document', None)
  226. if self.document is None:
  227. self.document = getattr(options, 'model', None)
  228. self.model = self.document
  229. meta = getattr(self.document, '_meta', {})
  230. # set up the document meta wrapper if document meta is a dict
  231. if self.document is not None and \
  232. not isinstance(meta, DocumentMetaWrapper):
  233. self.document._meta = DocumentMetaWrapper(self.document)
  234. self.fields = getattr(options, 'fields', None)
  235. self.exclude = getattr(options, 'exclude', None)
  236. self.widgets = getattr(options, 'widgets', None)
  237. self.embedded_field = getattr(options, 'embedded_field_name', None)
  238. self.formfield_generator = getattr(options, 'formfield_generator',
  239. _fieldgenerator)
  240. self._dont_save = []
  241. class DocumentFormMetaclass(type):
  242. def __new__(cls, name, bases, attrs):
  243. formfield_callback = attrs.pop('formfield_callback', None)
  244. try:
  245. parents = [
  246. b for b in bases
  247. if issubclass(b, DocumentForm) or
  248. issubclass(b, EmbeddedDocumentForm)
  249. ]
  250. except NameError:
  251. # We are defining DocumentForm itself.
  252. parents = None
  253. declared_fields = get_declared_fields(bases, attrs, False)
  254. new_class = super(DocumentFormMetaclass, cls).__new__(cls, name,
  255. bases, attrs)
  256. if not parents:
  257. return new_class
  258. if 'media' not in attrs:
  259. new_class.media = media_property(new_class)
  260. opts = new_class._meta = ModelFormOptions(
  261. getattr(new_class, 'Meta', None)
  262. )
  263. if opts.document:
  264. formfield_generator = getattr(opts,
  265. 'formfield_generator',
  266. _fieldgenerator)
  267. # If a model is defined, extract form fields from it.
  268. fields = fields_for_document(opts.document, opts.fields,
  269. opts.exclude, opts.widgets,
  270. formfield_callback,
  271. formfield_generator)
  272. # make sure opts.fields doesn't specify an invalid field
  273. none_document_fields = [k for k, v in fields.items() if not v]
  274. missing_fields = (set(none_document_fields) -
  275. set(declared_fields.keys()))
  276. if missing_fields:
  277. message = 'Unknown field(s) (%s) specified for %s'
  278. message = message % (', '.join(missing_fields),
  279. opts.model.__name__)
  280. raise FieldError(message)
  281. # Override default model fields with any custom declared ones
  282. # (plus, include all the other declared fields).
  283. fields.update(declared_fields)
  284. else:
  285. fields = declared_fields
  286. new_class.declared_fields = declared_fields
  287. new_class.base_fields = fields
  288. return new_class
  289. class BaseDocumentForm(BaseForm):
  290. def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
  291. initial=None, error_class=ErrorList, label_suffix=':',
  292. empty_permitted=False, instance=None):
  293. opts = self._meta
  294. if instance is None:
  295. if opts.document is None:
  296. raise ValueError('A document class must be provided.')
  297. # if we didn't get an instance, instantiate a new one
  298. self.instance = opts.document
  299. object_data = {}
  300. else:
  301. self.instance = instance
  302. object_data = document_to_dict(instance, opts.fields, opts.exclude)
  303. # if initial was provided, it should override the values from instance
  304. if initial is not None:
  305. object_data.update(initial)
  306. # self._validate_unique will be set to True by BaseModelForm.clean().
  307. # It is False by default so overriding self.clean() and failing to call
  308. # super will stop validate_unique from being called.
  309. self._validate_unique = False
  310. super(BaseDocumentForm, self).__init__(data, files, auto_id, prefix,
  311. object_data, error_class,
  312. label_suffix, empty_permitted)
  313. def _update_errors(self, message_dict):
  314. for k, v in list(message_dict.items()):
  315. if k != NON_FIELD_ERRORS:
  316. self._errors.setdefault(k, self.error_class()).extend(v)
  317. # Remove the invalid data from the cleaned_data dict
  318. if k in self.cleaned_data:
  319. del self.cleaned_data[k]
  320. if NON_FIELD_ERRORS in message_dict:
  321. messages = message_dict[NON_FIELD_ERRORS]
  322. self._errors.setdefault(NON_FIELD_ERRORS,
  323. self.error_class()).extend(messages)
  324. def _get_validation_exclusions(self):
  325. """
  326. For backwards-compatibility, several types of fields need to be
  327. excluded from model validation. See the following tickets for
  328. details: #12507, #12521, #12553
  329. """
  330. exclude = []
  331. # Build up a list of fields that should be excluded from model field
  332. # validation and unique checks.
  333. for f in self.instance._fields.values():
  334. # Exclude fields that aren't on the form. The developer may be
  335. # adding these values to the model after form validation.
  336. if f.name not in self.fields:
  337. exclude.append(f.name)
  338. # Don't perform model validation on fields that were defined
  339. # manually on the form and excluded via the ModelForm's Meta
  340. # class. See #12901.
  341. elif self._meta.fields and f.name not in self._meta.fields:
  342. exclude.append(f.name)
  343. elif self._meta.exclude and f.name in self._meta.exclude:
  344. exclude.append(f.name)
  345. # Exclude fields that failed form validation. There's no need for
  346. # the model fields to validate them as well.
  347. elif f.name in list(self._errors.keys()):
  348. exclude.append(f.name)
  349. # Exclude empty fields that are not required by the form, if the
  350. # underlying model field is required. This keeps the model field
  351. # from raising a required error. Note: don't exclude the field from
  352. # validaton if the model field allows blanks. If it does, the blank
  353. # value may be included in a unique check, so cannot be excluded
  354. # from validation.
  355. else:
  356. field_value = self.cleaned_data.get(f.name, None)
  357. if not f.required and field_value in EMPTY_VALUES:
  358. exclude.append(f.name)
  359. return exclude
  360. def clean(self):
  361. self._validate_unique = True
  362. return self.cleaned_data
  363. def _post_clean(self):
  364. opts = self._meta
  365. # Update the model instance with self.cleaned_data.
  366. self.instance = construct_instance(self, self.instance, opts.fields,
  367. opts.exclude)
  368. changed_fields = getattr(self.instance, '_changed_fields', [])
  369. exclude = self._get_validation_exclusions()
  370. try:
  371. for f in self.instance._fields.values():
  372. value = getattr(self.instance, f.name)
  373. if f.name not in exclude:
  374. f.validate(value)
  375. elif value in EMPTY_VALUES and f.name not in changed_fields:
  376. # mongoengine chokes on empty strings for fields
  377. # that are not required. Clean them up here, though
  378. # this is maybe not the right place :-)
  379. setattr(self.instance, f.name, None)
  380. #opts._dont_save.append(f.name)
  381. except ValidationError as e:
  382. err = {f.name: [e.message]}
  383. self._update_errors(err)
  384. # Call validate() on the document. Since mongoengine
  385. # does not provide an argument to specify which fields
  386. # should be excluded during validation, we replace
  387. # instance._fields_ordered with a version that does
  388. # not include excluded fields. The attribute gets
  389. # restored after validation.
  390. original_fields = self.instance._fields_ordered
  391. self.instance._fields_ordered = tuple(
  392. [f for f in original_fields if f not in exclude]
  393. )
  394. try:
  395. self.instance.validate()
  396. except ValidationError as e:
  397. if MONGO_NON_FIELD_ERRORS in e.errors:
  398. error = e.errors.get(MONGO_NON_FIELD_ERRORS)
  399. else:
  400. error = e.message
  401. self._update_errors({NON_FIELD_ERRORS: [error, ]})
  402. finally:
  403. self.instance._fields_ordered = original_fields
  404. # Validate uniqueness if needed.
  405. if self._validate_unique:
  406. self.validate_unique()
  407. def validate_unique(self):
  408. """
  409. Validates unique constrains on the document.
  410. unique_with is supported now.
  411. """
  412. errors = []
  413. exclude = self._get_validation_exclusions()
  414. for f in self.instance._fields.values():
  415. if f.unique and f.name not in exclude:
  416. filter_kwargs = {
  417. f.name: getattr(self.instance, f.name),
  418. 'q_obj': None,
  419. }
  420. if f.unique_with:
  421. for u_with in f.unique_with:
  422. u_with_field = self.instance._fields[u_with]
  423. u_with_attr = getattr(self.instance, u_with)
  424. # handling ListField(ReferenceField()) sucks big time
  425. # What we need to do is construct a Q object that
  426. # queries for the pk of every list entry and only
  427. # accepts lists with the same length as our list
  428. if isinstance(u_with_field, ListField) and \
  429. isinstance(u_with_field.field, ReferenceField):
  430. q_list = [Q(**{u_with: k.pk}) for k in u_with_attr]
  431. q = reduce(lambda x, y: x & y, q_list)
  432. size_key = '%s__size' % u_with
  433. q = q & Q(**{size_key: len(u_with_attr)})
  434. filter_kwargs['q_obj'] = q & filter_kwargs['q_obj']
  435. else:
  436. filter_kwargs[u_with] = u_with_attr
  437. qs = self.instance.__class__.objects.clone()
  438. qs = qs.no_dereference().filter(**filter_kwargs)
  439. # Exclude the current object from the query if we are editing
  440. # an instance (as opposed to creating a new one)
  441. if self.instance.pk is not None:
  442. qs = qs.filter(pk__ne=self.instance.pk)
  443. if qs.count() > 0:
  444. message = _("%s with this %s already exists.") % (
  445. str(capfirst(self.instance._meta.verbose_name)),
  446. str(pretty_name(f.name))
  447. )
  448. err_dict = {f.name: [message]}
  449. self._update_errors(err_dict)
  450. errors.append(err_dict)
  451. return errors
  452. def save(self, commit=True):
  453. """
  454. Saves this ``form``'s cleaned_data into model instance
  455. ``self.instance``.
  456. If commit=True, then the changes to ``instance`` will be saved to the
  457. database. Returns ``instance``.
  458. """
  459. try:
  460. if self.instance.pk is None:
  461. fail_message = 'created'
  462. else:
  463. fail_message = 'changed'
  464. except (KeyError, AttributeError):
  465. fail_message = 'embedded document saved'
  466. obj = save_instance(self, self.instance, self._meta.fields,
  467. fail_message, commit, construct=False)
  468. return obj
  469. save.alters_data = True
  470. class DocumentForm(with_metaclass(DocumentFormMetaclass, BaseDocumentForm)):
  471. pass
  472. def documentform_factory(document, form=DocumentForm, fields=None,
  473. exclude=None, formfield_callback=None):
  474. # Build up a list of attributes that the Meta object will have.
  475. attrs = {'document': document, 'model': document}
  476. if fields is not None:
  477. attrs['fields'] = fields
  478. if exclude is not None:
  479. attrs['exclude'] = exclude
  480. # If parent form class already has an inner Meta, the Meta we're
  481. # creating needs to inherit from the parent's inner meta.
  482. parent = (object,)
  483. if hasattr(form, 'Meta'):
  484. parent = (form.Meta, object)
  485. Meta = type('Meta', parent, attrs)
  486. # Give this new form class a reasonable name.
  487. if isinstance(document, type):
  488. doc_inst = document()
  489. else:
  490. doc_inst = document
  491. class_name = doc_inst.__class__.__name__ + 'Form'
  492. # Class attributes for the new form class.
  493. form_class_attrs = {
  494. 'Meta': Meta,
  495. 'formfield_callback': formfield_callback
  496. }
  497. return DocumentFormMetaclass(class_name, (form,), form_class_attrs)
  498. class EmbeddedDocumentForm(with_metaclass(DocumentFormMetaclass,
  499. BaseDocumentForm)):
  500. def __init__(self, parent_document, data=None, files=None, position=None,
  501. *args, **kwargs):
  502. if self._meta.embedded_field is not None and not \
  503. self._meta.embedded_field in parent_document._fields:
  504. raise FieldError("Parent document must have field %s" %
  505. self._meta.embedded_field)
  506. instance = kwargs.pop('instance', None)
  507. if isinstance(parent_document._fields.get(self._meta.embedded_field),
  508. ListField):
  509. # if we received a list position of the instance and no instance
  510. # load the instance from the parent document and proceed as normal
  511. if instance is None and position is not None:
  512. instance = getattr(parent_document,
  513. self._meta.embedded_field)[position]
  514. # same as above only the other way around. Note: Mongoengine
  515. # defines equality as having the same data, so if you have 2
  516. # objects with the same data the first one will be edited. That
  517. # may or may not be the right one.
  518. if instance is not None and position is None:
  519. emb_list = getattr(parent_document, self._meta.embedded_field)
  520. position = next(
  521. (i for i, obj in enumerate(emb_list) if obj == instance),
  522. None
  523. )
  524. super(EmbeddedDocumentForm, self).__init__(data=data, files=files,
  525. instance=instance, *args,
  526. **kwargs)
  527. self.parent_document = parent_document
  528. self.position = position
  529. def save(self, commit=True):
  530. """If commit is True the embedded document is added to the parent
  531. document. Otherwise the parent_document is left untouched and the
  532. embedded is returned as usual.
  533. """
  534. if self.errors:
  535. raise ValueError("The %s could not be saved because the data"
  536. "didn't validate." %
  537. self.instance.__class__.__name__)
  538. if commit:
  539. field = self.parent_document._fields.get(self._meta.embedded_field)
  540. if isinstance(field, ListField) and self.position is None:
  541. # no position given, simply appending to ListField
  542. try:
  543. self.parent_document.update(**{
  544. "push__" + self._meta.embedded_field: self.instance
  545. })
  546. except:
  547. raise OperationError("The %s could not be appended." %
  548. self.instance.__class__.__name__)
  549. elif isinstance(field, ListField) and self.position is not None:
  550. # updating ListField at given position
  551. try:
  552. self.parent_document.update(**{
  553. "__".join(("set", self._meta.embedded_field,
  554. str(self.position))): self.instance
  555. })
  556. except:
  557. raise OperationError("The %s could not be updated at "
  558. "position %d." %
  559. (self.instance.__class__.__name__,
  560. self.position))
  561. else:
  562. # not a listfield on parent, treat as an embedded field
  563. setattr(self.parent_document, self._meta.embedded_field,
  564. self.instance)
  565. self.parent_document.save()
  566. return self.instance
  567. class BaseDocumentFormSet(BaseFormSet):
  568. """
  569. A ``FormSet`` for editing a queryset and/or adding new objects to it.
  570. """
  571. def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
  572. queryset=None, **kwargs):
  573. if not isinstance(queryset, (list, BaseQuerySet)):
  574. queryset = [queryset]
  575. self.queryset = queryset
  576. self._queryset = self.queryset
  577. self.initial = self.construct_initial()
  578. defaults = {'data': data, 'files': files, 'auto_id': auto_id,
  579. 'prefix': prefix, 'initial': self.initial}
  580. defaults.update(kwargs)
  581. super(BaseDocumentFormSet, self).__init__(**defaults)
  582. def construct_initial(self):
  583. initial = []
  584. try:
  585. for d in self.get_queryset():
  586. initial.append(document_to_dict(d))
  587. except TypeError:
  588. pass
  589. return initial
  590. def initial_form_count(self):
  591. """Returns the number of forms that are required in this FormSet."""
  592. if not (self.data or self.files):
  593. return len(self.get_queryset())
  594. return super(BaseDocumentFormSet, self).initial_form_count()
  595. def get_queryset(self):
  596. qs = self._queryset or []
  597. return qs
  598. def save_object(self, form):
  599. obj = form.save(commit=False)
  600. return obj
  601. def save(self, commit=True):
  602. """
  603. Saves model instances for every form, adding and changing instances
  604. as necessary, and returns the list of instances.
  605. """
  606. saved = []
  607. for form in self.forms:
  608. if not form.has_changed() and not form in self.initial_forms:
  609. continue
  610. obj = self.save_object(form)
  611. if form.cleaned_data.get("DELETE", False):
  612. try:
  613. obj.delete()
  614. except AttributeError:
  615. # if it has no delete method it is an embedded object. We
  616. # just don't add to the list and it's gone. Cool huh?
  617. continue
  618. if commit:
  619. obj.save()
  620. saved.append(obj)
  621. return saved
  622. def clean(self):
  623. self.validate_unique()
  624. def validate_unique(self):
  625. errors = []
  626. for form in self.forms:
  627. if not hasattr(form, 'cleaned_data'):
  628. continue
  629. errors += form.validate_unique()
  630. if errors:
  631. raise ValidationError(errors)
  632. def get_date_error_message(self, date_check):
  633. return ugettext("Please correct the duplicate data for %(field_name)s "
  634. "which must be unique for the %(lookup)s "
  635. "in %(date_field)s.") % {
  636. 'field_name': date_check[2],
  637. 'date_field': date_check[3],
  638. 'lookup': str(date_check[1]),
  639. }
  640. def get_form_error(self):
  641. return ugettext("Please correct the duplicate values below.")
  642. def documentformset_factory(document, form=DocumentForm,
  643. formfield_callback=None,
  644. formset=BaseDocumentFormSet,
  645. extra=1, can_delete=False, can_order=False,
  646. max_num=None, fields=None, exclude=None):
  647. """
  648. Returns a FormSet class for the given Django model class.
  649. """
  650. form = documentform_factory(document, form=form, fields=fields,
  651. exclude=exclude,
  652. formfield_callback=formfield_callback)
  653. FormSet = formset_factory(form, formset, extra=extra, max_num=max_num,
  654. can_order=can_order, can_delete=can_delete)
  655. FormSet.model = document
  656. FormSet.document = document
  657. return FormSet
  658. class BaseInlineDocumentFormSet(BaseDocumentFormSet):
  659. """
  660. A formset for child objects related to a parent.
  661. self.instance -> the document containing the inline objects
  662. """
  663. def __init__(self, data=None, files=None, instance=None,
  664. save_as_new=False, prefix=None, queryset=[], **kwargs):
  665. self.instance = instance
  666. self.save_as_new = save_as_new
  667. super(BaseInlineDocumentFormSet, self).__init__(data, files,
  668. prefix=prefix,
  669. queryset=queryset,
  670. **kwargs)
  671. def initial_form_count(self):
  672. if self.save_as_new:
  673. return 0
  674. return super(BaseInlineDocumentFormSet, self).initial_form_count()
  675. #@classmethod
  676. def get_default_prefix(cls):
  677. return cls.document.__name__.lower()
  678. get_default_prefix = classmethod(get_default_prefix)
  679. def add_fields(self, form, index):
  680. super(BaseInlineDocumentFormSet, self).add_fields(form, index)
  681. # Add the generated field to form._meta.fields if it's defined to make
  682. # sure validation isn't skipped on that field.
  683. if form._meta.fields:
  684. if isinstance(form._meta.fields, tuple):
  685. form._meta.fields = list(form._meta.fields)
  686. #form._meta.fields.append(self.fk.name)
  687. def get_unique_error_message(self, unique_check):
  688. unique_check = [
  689. field for field in unique_check if field != self.fk.name
  690. ]
  691. return super(BaseInlineDocumentFormSet, self).get_unique_error_message(
  692. unique_check
  693. )
  694. def inlineformset_factory(document, form=DocumentForm,
  695. formset=BaseInlineDocumentFormSet,
  696. fields=None, exclude=None,
  697. extra=1, can_order=False, can_delete=True,
  698. max_num=None, formfield_callback=None):
  699. """
  700. Returns an ``InlineFormSet`` for the given kwargs.
  701. You must provide ``fk_name`` if ``model`` has more than one ``ForeignKey``
  702. to ``parent_model``.
  703. """
  704. kwargs = {
  705. 'form': form,
  706. 'formfield_callback': formfield_callback,
  707. 'formset': formset,
  708. 'extra': extra,
  709. 'can_delete': can_delete,
  710. 'can_order': can_order,
  711. 'fields': fields,
  712. 'exclude': exclude,
  713. 'max_num': max_num,
  714. }
  715. FormSet = documentformset_factory(document, **kwargs)
  716. return FormSet
  717. class EmbeddedDocumentFormSet(BaseDocumentFormSet):
  718. def __init__(self, data=None, files=None, save_as_new=False,
  719. prefix=None, queryset=[], parent_document=None, **kwargs):
  720. if parent_document is not None:
  721. self.parent_document = parent_document
  722. if 'instance' in kwargs:
  723. instance = kwargs.pop('instance')
  724. if parent_document is None:
  725. self.parent_document = instance
  726. queryset = getattr(self.parent_document,
  727. self.form._meta.embedded_field)
  728. super(EmbeddedDocumentFormSet, self).__init__(data, files, save_as_new,
  729. prefix, queryset,
  730. **kwargs)
  731. def _construct_form(self, i, **kwargs):
  732. defaults = {'parent_document': self.parent_document}
  733. # add position argument to the form. Otherwise we will spend
  734. # a huge amount of time iterating over the list field on form __init__
  735. emb_list = getattr(self.parent_document,
  736. self.form._meta.embedded_field)
  737. if emb_list is not None and len(emb_list) > i:
  738. defaults['position'] = i
  739. defaults.update(kwargs)
  740. form = super(EmbeddedDocumentFormSet, self)._construct_form(
  741. i, **defaults)
  742. return form
  743. @classmethod
  744. def get_default_prefix(cls):
  745. return cls.document.__name__.lower()
  746. @property
  747. def empty_form(self):
  748. form = self.form(
  749. self.parent_document,
  750. auto_id=self.auto_id,
  751. prefix=self.add_prefix('__prefix__'),
  752. empty_permitted=True,
  753. )
  754. self.add_fields(form, None)
  755. return form
  756. def save(self, commit=True):
  757. # Don't try to save the new documents. Embedded objects don't have
  758. # a save method anyway.
  759. objs = super(EmbeddedDocumentFormSet, self).save(commit=False)
  760. objs = objs or []
  761. if commit and self.parent_document is not None:
  762. field = self.parent_document._fields.get(self.form._meta.embedded_field, None)
  763. if isinstance(field, EmbeddedDocumentField):
  764. try:
  765. obj = objs[0]
  766. except IndexError:
  767. obj = None
  768. setattr(self.parent_document, self.form._meta.embedded_field, obj)
  769. else:
  770. setattr(self.parent_document, self.form._meta.embedded_field, objs)
  771. self.parent_document.save()
  772. return objs
  773. def _get_embedded_field(parent_doc, document, emb_name=None, can_fail=False):
  774. if emb_name:
  775. emb_fields = [f for f in parent_doc._fields.values() if f.name == emb_name]
  776. if len(emb_fields) == 1:
  777. field = emb_fields[0]
  778. if not isinstance(field, (EmbeddedDocumentField, ListField)) or \
  779. (isinstance(field, EmbeddedDocumentField) and field.document_type != document) or \
  780. (isinstance(field, ListField) and
  781. isinstance(field.field, EmbeddedDocumentField) and
  782. field.field.document_type != document):
  783. raise Exception("emb_name '%s' is not a EmbeddedDocumentField or not a ListField to %s" % (emb_name, document))
  784. elif len(emb_fields) == 0:
  785. raise Exception("%s has no field named '%s'" % (parent_doc, emb_name))
  786. else:
  787. emb_fields = [
  788. f for f in parent_doc._fields.values()
  789. if (isinstance(field, EmbeddedDocumentField) and field.document_type == document) or \
  790. (isinstance(field, ListField) and
  791. isinstance(field.field, EmbeddedDocumentField) and
  792. field.field.document_type == document)
  793. ]
  794. if len(emb_fields) == 1:
  795. field = emb_fields[0]
  796. elif len(emb_fields) == 0:
  797. if can_fail:
  798. return
  799. raise Exception("%s has no EmbeddedDocumentField or ListField to %s" % (parent_doc, document))
  800. else:
  801. raise Exception("%s has more than 1 EmbeddedDocumentField to %s" % (parent_doc, document))
  802. return field
  803. def embeddedformset_factory(document, parent_document,
  804. form=EmbeddedDocumentForm,
  805. formset=EmbeddedDocumentFormSet,
  806. embedded_name=None,
  807. fields=None, exclude=None,
  808. extra=3, can_order=False, can_delete=True,
  809. max_num=None, formfield_callback=None):
  810. """
  811. Returns an ``InlineFormSet`` for the given kwargs.
  812. You must provide ``fk_name`` if ``model`` has more than one ``ForeignKey``
  813. to ``parent_model``.
  814. """
  815. emb_field = _get_embedded_field(parent_document, document, emb_name=embedded_name)
  816. if isinstance(emb_field, EmbeddedDocumentField):
  817. max_num = 1
  818. kwargs = {
  819. 'form': form,
  820. 'formfield_callback': formfield_callback,
  821. 'formset': formset,
  822. 'extra': extra,
  823. 'can_delete': can_delete,
  824. 'can_order': can_order,
  825. 'fields': fields,
  826. 'exclude': exclude,
  827. 'max_num': max_num,
  828. }
  829. FormSet = documentformset_factory(document, **kwargs)
  830. FormSet.form._meta.embedded_field = emb_field.name
  831. return FormSet