123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966 |
- import os
- import itertools
- from collections import Callable, OrderedDict
- from functools import reduce
- from django.forms.forms import (BaseForm, get_declared_fields,
- NON_FIELD_ERRORS, pretty_name)
- from django.forms.widgets import media_property
- from django.core.exceptions import FieldError
- from django.core.validators import EMPTY_VALUES
- from django.forms.util import ErrorList
- from django.forms.formsets import BaseFormSet, formset_factory
- from django.utils.translation import ugettext_lazy as _, ugettext
- from django.utils.text import capfirst, get_valid_filename
- from mongoengine.fields import (ObjectIdField, ListField, ReferenceField,
- FileField, MapField, EmbeddedDocumentField)
- try:
- from mongoengine.base import ValidationError
- except ImportError:
- from mongoengine.errors import ValidationError
- from mongoengine.queryset import OperationError, Q
- from mongoengine.queryset.base import BaseQuerySet
- from mongoengine.connection import get_db, DEFAULT_CONNECTION_NAME
- from mongoengine.base import NON_FIELD_ERRORS as MONGO_NON_FIELD_ERRORS
- from gridfs import GridFS
- from mongodbforms.documentoptions import DocumentMetaWrapper
- from mongodbforms.util import with_metaclass, load_field_generator
- _fieldgenerator = load_field_generator()
- def _get_unique_filename(name, db_alias=DEFAULT_CONNECTION_NAME,
- collection_name='fs'):
- fs = GridFS(get_db(db_alias), collection_name)
- file_root, file_ext = os.path.splitext(get_valid_filename(name))
- count = itertools.count(1)
- while fs.exists(filename=name):
- # file_ext includes the dot.
- name = os.path.join("%s_%s%s" % (file_root, next(count), file_ext))
- return name
-
- def _save_iterator_file(field, instance, uploaded_file, file_data=None):
- """
- Takes care of saving a file for a list field. Returns a Mongoengine
- fileproxy object or the file field.
- """
- # for a new file we need a new proxy object
- if file_data is None:
- file_data = field.field.get_proxy_obj(key=field.name,
- instance=instance)
-
- if file_data.instance is None:
- file_data.instance = instance
- if file_data.key is None:
- file_data.key = field.name
-
- if file_data.grid_id:
- file_data.delete()
-
- uploaded_file.seek(0)
- filename = _get_unique_filename(uploaded_file.name, field.field.db_alias,
- field.field.collection_name)
- file_data.put(uploaded_file, content_type=uploaded_file.content_type,
- filename=filename)
- file_data.close()
-
- return file_data
- def construct_instance(form, instance, fields=None, exclude=None):
- """
- Constructs and returns a document instance from the bound ``form``'s
- ``cleaned_data``, but does not save the returned instance to the
- database.
- """
- cleaned_data = form.cleaned_data
- file_field_list = []
-
- # check wether object is instantiated
- if isinstance(instance, type):
- instance = instance()
-
- for f in instance._fields.values():
- if isinstance(f, ObjectIdField):
- continue
- if not f.name in cleaned_data:
- continue
- if fields is not None and f.name not in fields:
- continue
- if exclude and f.name in exclude:
- continue
- # Defer saving file-type fields until after the other fields, so a
- # callable upload_to can use the values from other fields.
- if isinstance(f, FileField) or \
- (isinstance(f, (MapField, ListField)) and
- isinstance(f.field, FileField)):
- file_field_list.append(f)
- else:
- setattr(instance, f.name, cleaned_data.get(f.name))
- for f in file_field_list:
- if isinstance(f, MapField):
- map_field = getattr(instance, f.name)
- uploads = cleaned_data[f.name]
- for key, uploaded_file in uploads.items():
- if uploaded_file is None:
- continue
- file_data = map_field.get(key, None)
- map_field[key] = _save_iterator_file(f, instance,
- uploaded_file, file_data)
- setattr(instance, f.name, map_field)
- elif isinstance(f, ListField):
- list_field = getattr(instance, f.name)
- uploads = cleaned_data[f.name]
- for i, uploaded_file in enumerate(uploads):
- if uploaded_file is None:
- continue
- try:
- file_data = list_field[i]
- except IndexError:
- file_data = None
- file_obj = _save_iterator_file(f, instance,
- uploaded_file, file_data)
- try:
- list_field[i] = file_obj
- except IndexError:
- list_field.append(file_obj)
- setattr(instance, f.name, list_field)
- else:
- field = getattr(instance, f.name)
- upload = cleaned_data[f.name]
- if upload is None:
- continue
-
- try:
- upload.file.seek(0)
- # delete first to get the names right
- if field.grid_id:
- field.delete()
- filename = _get_unique_filename(upload.name, f.db_alias,
- f.collection_name)
- field.put(upload, content_type=upload.content_type,
- filename=filename)
- setattr(instance, f.name, field)
- except AttributeError:
- # file was already uploaded and not changed during edit.
- # upload is already the gridfsproxy object we need.
- upload.get()
- setattr(instance, f.name, upload)
-
- return instance
- def save_instance(form, instance, fields=None, fail_message='saved',
- commit=True, exclude=None, construct=True):
- """
- Saves bound Form ``form``'s cleaned_data into document ``instance``.
- If commit=True, then the changes to ``instance`` will be saved to the
- database. Returns ``instance``.
- If construct=False, assume ``instance`` has already been constructed and
- just needs to be saved.
- """
- if construct:
- instance = construct_instance(form, instance, fields, exclude)
-
- if form.errors:
- raise ValueError("The %s could not be %s because the data didn't"
- " validate." % (instance.__class__.__name__,
- fail_message))
-
- if commit and hasattr(instance, 'save'):
- # see BaseDocumentForm._post_clean for an explanation
- #if len(form._meta._dont_save) > 0:
- # data = instance._data
- # new_data = dict([(n, f) for n, f in data.items() if not n \
- # in form._meta._dont_save])
- # instance._data = new_data
- # instance.save()
- # instance._data = data
- #else:
- instance.save()
- return instance
- def document_to_dict(instance, fields=None, exclude=None):
- """
- Returns a dict containing the data in ``instance`` suitable for passing as
- a Form's ``initial`` keyword argument.
- ``fields`` is an optional list of field names. If provided, only the named
- fields will be included in the returned dict.
- ``exclude`` is an optional list of field names. If provided, the named
- fields will be excluded from the returned dict, even if they are listed in
- the ``fields`` argument.
- """
- data = {}
- for f in instance._fields.values():
- if fields and not f.name in fields:
- continue
- if exclude and f.name in exclude:
- continue
- data[f.name] = getattr(instance, f.name, '')
- return data
- def fields_for_document(document, fields=None, exclude=None, widgets=None,
- formfield_callback=None,
- field_generator=_fieldgenerator):
- """
- Returns a ``SortedDict`` containing form fields for the given model.
- ``fields`` is an optional list of field names. If provided, only the named
- fields will be included in the returned fields.
- ``exclude`` is an optional list of field names. If provided, the named
- fields will be excluded from the returned fields, even if they are listed
- in the ``fields`` argument.
- """
- field_list = []
- if isinstance(field_generator, type):
- field_generator = field_generator()
-
- if formfield_callback and not isinstance(formfield_callback, Callable):
- raise TypeError('formfield_callback must be a function or callable')
-
- for name in document._fields_ordered:
- f = document._fields.get(name)
- if isinstance(f, ObjectIdField):
- continue
- if fields and not f.name in fields:
- continue
- if exclude and f.name in exclude:
- continue
- if widgets and f.name in widgets:
- kwargs = {'widget': widgets[f.name]}
- else:
- kwargs = {}
- if formfield_callback:
- formfield = formfield_callback(f, **kwargs)
- else:
- formfield = field_generator.generate(f, **kwargs)
- if formfield:
- field_list.append((f.name, formfield))
-
- field_dict = OrderedDict(field_list)
- if fields:
- field_dict = OrderedDict(
- [(f, field_dict.get(f)) for f in fields
- if ((not exclude) or (exclude and f not in exclude))]
- )
- return field_dict
- class ModelFormOptions(object):
- def __init__(self, options=None):
- # document class can be declared with 'document =' or 'model ='
- self.document = getattr(options, 'document', None)
- if self.document is None:
- self.document = getattr(options, 'model', None)
-
- self.model = self.document
- meta = getattr(self.document, '_meta', {})
- # set up the document meta wrapper if document meta is a dict
- if self.document is not None and \
- not isinstance(meta, DocumentMetaWrapper):
- self.document._meta = DocumentMetaWrapper(self.document)
- self.fields = getattr(options, 'fields', None)
- self.exclude = getattr(options, 'exclude', None)
- self.widgets = getattr(options, 'widgets', None)
- self.embedded_field = getattr(options, 'embedded_field_name', None)
- self.formfield_generator = getattr(options, 'formfield_generator',
- _fieldgenerator)
-
- self._dont_save = []
-
-
- class DocumentFormMetaclass(type):
- def __new__(cls, name, bases, attrs):
- formfield_callback = attrs.pop('formfield_callback', None)
- try:
- parents = [
- b for b in bases
- if issubclass(b, DocumentForm) or
- issubclass(b, EmbeddedDocumentForm)
- ]
- except NameError:
- # We are defining DocumentForm itself.
- parents = None
- declared_fields = get_declared_fields(bases, attrs, False)
- new_class = super(DocumentFormMetaclass, cls).__new__(cls, name,
- bases, attrs)
- if not parents:
- return new_class
- if 'media' not in attrs:
- new_class.media = media_property(new_class)
-
- opts = new_class._meta = ModelFormOptions(
- getattr(new_class, 'Meta', None)
- )
- if opts.document:
- formfield_generator = getattr(opts,
- 'formfield_generator',
- _fieldgenerator)
-
- # If a model is defined, extract form fields from it.
- fields = fields_for_document(opts.document, opts.fields,
- opts.exclude, opts.widgets,
- formfield_callback,
- formfield_generator)
- # make sure opts.fields doesn't specify an invalid field
- none_document_fields = [k for k, v in fields.items() if not v]
- missing_fields = (set(none_document_fields) -
- set(declared_fields.keys()))
- if missing_fields:
- message = 'Unknown field(s) (%s) specified for %s'
- message = message % (', '.join(missing_fields),
- opts.model.__name__)
- raise FieldError(message)
- # Override default model fields with any custom declared ones
- # (plus, include all the other declared fields).
- fields.update(declared_fields)
- else:
- fields = declared_fields
-
- new_class.declared_fields = declared_fields
- new_class.base_fields = fields
- return new_class
-
-
- class BaseDocumentForm(BaseForm):
- def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
- initial=None, error_class=ErrorList, label_suffix=':',
- empty_permitted=False, instance=None):
-
- opts = self._meta
-
- if instance is None:
- if opts.document is None:
- raise ValueError('A document class must be provided.')
- # if we didn't get an instance, instantiate a new one
- self.instance = opts.document
- object_data = {}
- else:
- self.instance = instance
- object_data = document_to_dict(instance, opts.fields, opts.exclude)
-
- # if initial was provided, it should override the values from instance
- if initial is not None:
- object_data.update(initial)
-
- # self._validate_unique will be set to True by BaseModelForm.clean().
- # It is False by default so overriding self.clean() and failing to call
- # super will stop validate_unique from being called.
- self._validate_unique = False
- super(BaseDocumentForm, self).__init__(data, files, auto_id, prefix,
- object_data, error_class,
- label_suffix, empty_permitted)
- def _update_errors(self, message_dict):
- for k, v in list(message_dict.items()):
- if k != NON_FIELD_ERRORS:
- self._errors.setdefault(k, self.error_class()).extend(v)
- # Remove the invalid data from the cleaned_data dict
- if k in self.cleaned_data:
- del self.cleaned_data[k]
- if NON_FIELD_ERRORS in message_dict:
- messages = message_dict[NON_FIELD_ERRORS]
- self._errors.setdefault(NON_FIELD_ERRORS,
- self.error_class()).extend(messages)
- def _get_validation_exclusions(self):
- """
- For backwards-compatibility, several types of fields need to be
- excluded from model validation. See the following tickets for
- details: #12507, #12521, #12553
- """
- exclude = []
- # Build up a list of fields that should be excluded from model field
- # validation and unique checks.
- for f in self.instance._fields.values():
- # Exclude fields that aren't on the form. The developer may be
- # adding these values to the model after form validation.
- if f.name not in self.fields:
- exclude.append(f.name)
- # Don't perform model validation on fields that were defined
- # manually on the form and excluded via the ModelForm's Meta
- # class. See #12901.
- elif self._meta.fields and f.name not in self._meta.fields:
- exclude.append(f.name)
- elif self._meta.exclude and f.name in self._meta.exclude:
- exclude.append(f.name)
- # Exclude fields that failed form validation. There's no need for
- # the model fields to validate them as well.
- elif f.name in list(self._errors.keys()):
- exclude.append(f.name)
- # Exclude empty fields that are not required by the form, if the
- # underlying model field is required. This keeps the model field
- # from raising a required error. Note: don't exclude the field from
- # validaton if the model field allows blanks. If it does, the blank
- # value may be included in a unique check, so cannot be excluded
- # from validation.
- else:
- field_value = self.cleaned_data.get(f.name, None)
- if not f.required and field_value in EMPTY_VALUES:
- exclude.append(f.name)
- return exclude
- def clean(self):
- self._validate_unique = True
- return self.cleaned_data
- def _post_clean(self):
- opts = self._meta
-
- # Update the model instance with self.cleaned_data.
- self.instance = construct_instance(self, self.instance, opts.fields,
- opts.exclude)
- changed_fields = getattr(self.instance, '_changed_fields', [])
- exclude = self._get_validation_exclusions()
- try:
- for f in self.instance._fields.values():
- value = getattr(self.instance, f.name)
- if f.name not in exclude:
- f.validate(value)
- elif value in EMPTY_VALUES and f.name not in changed_fields:
- # mongoengine chokes on empty strings for fields
- # that are not required. Clean them up here, though
- # this is maybe not the right place :-)
- setattr(self.instance, f.name, None)
- #opts._dont_save.append(f.name)
- except ValidationError as e:
- err = {f.name: [e.message]}
- self._update_errors(err)
- # Call validate() on the document. Since mongoengine
- # does not provide an argument to specify which fields
- # should be excluded during validation, we replace
- # instance._fields_ordered with a version that does
- # not include excluded fields. The attribute gets
- # restored after validation.
- original_fields = self.instance._fields_ordered
- self.instance._fields_ordered = tuple(
- [f for f in original_fields if f not in exclude]
- )
- try:
- self.instance.validate()
- except ValidationError as e:
- if MONGO_NON_FIELD_ERRORS in e.errors:
- error = e.errors.get(MONGO_NON_FIELD_ERRORS)
- else:
- error = e.message
- self._update_errors({NON_FIELD_ERRORS: [error, ]})
- finally:
- self.instance._fields_ordered = original_fields
- # Validate uniqueness if needed.
- if self._validate_unique:
- self.validate_unique()
- def validate_unique(self):
- """
- Validates unique constrains on the document.
- unique_with is supported now.
- """
- errors = []
- exclude = self._get_validation_exclusions()
- for f in self.instance._fields.values():
- if f.unique and f.name not in exclude:
- filter_kwargs = {
- f.name: getattr(self.instance, f.name),
- 'q_obj': None,
- }
- if f.unique_with:
- for u_with in f.unique_with:
- u_with_field = self.instance._fields[u_with]
- u_with_attr = getattr(self.instance, u_with)
- # handling ListField(ReferenceField()) sucks big time
- # What we need to do is construct a Q object that
- # queries for the pk of every list entry and only
- # accepts lists with the same length as our list
- if isinstance(u_with_field, ListField) and \
- isinstance(u_with_field.field, ReferenceField):
- q_list = [Q(**{u_with: k.pk}) for k in u_with_attr]
- q = reduce(lambda x, y: x & y, q_list)
- size_key = '%s__size' % u_with
- q = q & Q(**{size_key: len(u_with_attr)})
- filter_kwargs['q_obj'] = q & filter_kwargs['q_obj']
- else:
- filter_kwargs[u_with] = u_with_attr
- qs = self.instance.__class__.objects.clone()
- qs = qs.no_dereference().filter(**filter_kwargs)
- # Exclude the current object from the query if we are editing
- # an instance (as opposed to creating a new one)
- if self.instance.pk is not None:
- qs = qs.filter(pk__ne=self.instance.pk)
- if qs.count() > 0:
- message = _("%s with this %s already exists.") % (
- str(capfirst(self.instance._meta.verbose_name)),
- str(pretty_name(f.name))
- )
- err_dict = {f.name: [message]}
- self._update_errors(err_dict)
- errors.append(err_dict)
-
- return errors
-
- def save(self, commit=True):
- """
- Saves this ``form``'s cleaned_data into model instance
- ``self.instance``.
- If commit=True, then the changes to ``instance`` will be saved to the
- database. Returns ``instance``.
- """
- try:
- if self.instance.pk is None:
- fail_message = 'created'
- else:
- fail_message = 'changed'
- except (KeyError, AttributeError):
- fail_message = 'embedded document saved'
- obj = save_instance(self, self.instance, self._meta.fields,
- fail_message, commit, construct=False)
- return obj
- save.alters_data = True
- class DocumentForm(with_metaclass(DocumentFormMetaclass, BaseDocumentForm)):
- pass
-
- def documentform_factory(document, form=DocumentForm, fields=None,
- exclude=None, formfield_callback=None):
- # Build up a list of attributes that the Meta object will have.
- attrs = {'document': document, 'model': document}
- if fields is not None:
- attrs['fields'] = fields
- if exclude is not None:
- attrs['exclude'] = exclude
- # If parent form class already has an inner Meta, the Meta we're
- # creating needs to inherit from the parent's inner meta.
- parent = (object,)
- if hasattr(form, 'Meta'):
- parent = (form.Meta, object)
- Meta = type('Meta', parent, attrs)
- # Give this new form class a reasonable name.
- if isinstance(document, type):
- doc_inst = document()
- else:
- doc_inst = document
- class_name = doc_inst.__class__.__name__ + 'Form'
- # Class attributes for the new form class.
- form_class_attrs = {
- 'Meta': Meta,
- 'formfield_callback': formfield_callback
- }
- return DocumentFormMetaclass(class_name, (form,), form_class_attrs)
- class EmbeddedDocumentForm(with_metaclass(DocumentFormMetaclass,
- BaseDocumentForm)):
- def __init__(self, parent_document, data=None, files=None, position=None,
- *args, **kwargs):
- if self._meta.embedded_field is not None and not \
- self._meta.embedded_field in parent_document._fields:
- raise FieldError("Parent document must have field %s" %
- self._meta.embedded_field)
-
- instance = kwargs.pop('instance', None)
-
- if isinstance(parent_document._fields.get(self._meta.embedded_field),
- ListField):
- # if we received a list position of the instance and no instance
- # load the instance from the parent document and proceed as normal
- if instance is None and position is not None:
- instance = getattr(parent_document,
- self._meta.embedded_field)[position]
-
- # same as above only the other way around. Note: Mongoengine
- # defines equality as having the same data, so if you have 2
- # objects with the same data the first one will be edited. That
- # may or may not be the right one.
- if instance is not None and position is None:
- emb_list = getattr(parent_document, self._meta.embedded_field)
- position = next(
- (i for i, obj in enumerate(emb_list) if obj == instance),
- None
- )
-
- super(EmbeddedDocumentForm, self).__init__(data=data, files=files,
- instance=instance, *args,
- **kwargs)
- self.parent_document = parent_document
- self.position = position
-
- def save(self, commit=True):
- """If commit is True the embedded document is added to the parent
- document. Otherwise the parent_document is left untouched and the
- embedded is returned as usual.
- """
- if self.errors:
- raise ValueError("The %s could not be saved because the data"
- "didn't validate." %
- self.instance.__class__.__name__)
-
- if commit:
- field = self.parent_document._fields.get(self._meta.embedded_field)
- if isinstance(field, ListField) and self.position is None:
- # no position given, simply appending to ListField
- try:
- self.parent_document.update(**{
- "push__" + self._meta.embedded_field: self.instance
- })
- except:
- raise OperationError("The %s could not be appended." %
- self.instance.__class__.__name__)
- elif isinstance(field, ListField) and self.position is not None:
- # updating ListField at given position
- try:
- self.parent_document.update(**{
- "__".join(("set", self._meta.embedded_field,
- str(self.position))): self.instance
- })
- except:
- raise OperationError("The %s could not be updated at "
- "position %d." %
- (self.instance.__class__.__name__,
- self.position))
- else:
- # not a listfield on parent, treat as an embedded field
- setattr(self.parent_document, self._meta.embedded_field,
- self.instance)
- self.parent_document.save()
- return self.instance
- class BaseDocumentFormSet(BaseFormSet):
- """
- A ``FormSet`` for editing a queryset and/or adding new objects to it.
- """
- def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
- queryset=None, **kwargs):
- if not isinstance(queryset, (list, BaseQuerySet)):
- queryset = [queryset]
- self.queryset = queryset
- self._queryset = self.queryset
- self.initial = self.construct_initial()
- defaults = {'data': data, 'files': files, 'auto_id': auto_id,
- 'prefix': prefix, 'initial': self.initial}
- defaults.update(kwargs)
- super(BaseDocumentFormSet, self).__init__(**defaults)
- def construct_initial(self):
- initial = []
- try:
- for d in self.get_queryset():
- initial.append(document_to_dict(d))
- except TypeError:
- pass
- return initial
- def initial_form_count(self):
- """Returns the number of forms that are required in this FormSet."""
- if not (self.data or self.files):
- return len(self.get_queryset())
- return super(BaseDocumentFormSet, self).initial_form_count()
- def get_queryset(self):
- qs = self._queryset or []
- return qs
- def save_object(self, form):
- obj = form.save(commit=False)
- return obj
- def save(self, commit=True):
- """
- Saves model instances for every form, adding and changing instances
- as necessary, and returns the list of instances.
- """
- saved = []
- for form in self.forms:
- if not form.has_changed() and not form in self.initial_forms:
- continue
- obj = self.save_object(form)
- if form.cleaned_data.get("DELETE", False):
- try:
- obj.delete()
- except AttributeError:
- # if it has no delete method it is an embedded object. We
- # just don't add to the list and it's gone. Cool huh?
- continue
- if commit:
- obj.save()
- saved.append(obj)
- return saved
- def clean(self):
- self.validate_unique()
- def validate_unique(self):
- errors = []
- for form in self.forms:
- if not hasattr(form, 'cleaned_data'):
- continue
- errors += form.validate_unique()
-
- if errors:
- raise ValidationError(errors)
-
- def get_date_error_message(self, date_check):
- return ugettext("Please correct the duplicate data for %(field_name)s "
- "which must be unique for the %(lookup)s "
- "in %(date_field)s.") % {
- 'field_name': date_check[2],
- 'date_field': date_check[3],
- 'lookup': str(date_check[1]),
- }
- def get_form_error(self):
- return ugettext("Please correct the duplicate values below.")
- def documentformset_factory(document, form=DocumentForm,
- formfield_callback=None,
- formset=BaseDocumentFormSet,
- extra=1, can_delete=False, can_order=False,
- max_num=None, fields=None, exclude=None):
- """
- Returns a FormSet class for the given Django model class.
- """
- form = documentform_factory(document, form=form, fields=fields,
- exclude=exclude,
- formfield_callback=formfield_callback)
- FormSet = formset_factory(form, formset, extra=extra, max_num=max_num,
- can_order=can_order, can_delete=can_delete)
- FormSet.model = document
- FormSet.document = document
- return FormSet
- class BaseInlineDocumentFormSet(BaseDocumentFormSet):
- """
- A formset for child objects related to a parent.
-
- self.instance -> the document containing the inline objects
- """
- def __init__(self, data=None, files=None, instance=None,
- save_as_new=False, prefix=None, queryset=[], **kwargs):
- self.instance = instance
- self.save_as_new = save_as_new
-
- super(BaseInlineDocumentFormSet, self).__init__(data, files,
- prefix=prefix,
- queryset=queryset,
- **kwargs)
- def initial_form_count(self):
- if self.save_as_new:
- return 0
- return super(BaseInlineDocumentFormSet, self).initial_form_count()
- #@classmethod
- def get_default_prefix(cls):
- return cls.document.__name__.lower()
- get_default_prefix = classmethod(get_default_prefix)
-
- def add_fields(self, form, index):
- super(BaseInlineDocumentFormSet, self).add_fields(form, index)
- # Add the generated field to form._meta.fields if it's defined to make
- # sure validation isn't skipped on that field.
- if form._meta.fields:
- if isinstance(form._meta.fields, tuple):
- form._meta.fields = list(form._meta.fields)
- #form._meta.fields.append(self.fk.name)
- def get_unique_error_message(self, unique_check):
- unique_check = [
- field for field in unique_check if field != self.fk.name
- ]
- return super(BaseInlineDocumentFormSet, self).get_unique_error_message(
- unique_check
- )
- def inlineformset_factory(document, form=DocumentForm,
- formset=BaseInlineDocumentFormSet,
- fields=None, exclude=None,
- extra=1, can_order=False, can_delete=True,
- max_num=None, formfield_callback=None):
- """
- Returns an ``InlineFormSet`` for the given kwargs.
- You must provide ``fk_name`` if ``model`` has more than one ``ForeignKey``
- to ``parent_model``.
- """
- kwargs = {
- 'form': form,
- 'formfield_callback': formfield_callback,
- 'formset': formset,
- 'extra': extra,
- 'can_delete': can_delete,
- 'can_order': can_order,
- 'fields': fields,
- 'exclude': exclude,
- 'max_num': max_num,
- }
- FormSet = documentformset_factory(document, **kwargs)
- return FormSet
- class EmbeddedDocumentFormSet(BaseDocumentFormSet):
- def __init__(self, data=None, files=None, save_as_new=False,
- prefix=None, queryset=[], parent_document=None, **kwargs):
- if parent_document is not None:
- self.parent_document = parent_document
-
- if 'instance' in kwargs:
- instance = kwargs.pop('instance')
- if parent_document is None:
- self.parent_document = instance
-
- queryset = getattr(self.parent_document,
- self.form._meta.embedded_field)
-
- super(EmbeddedDocumentFormSet, self).__init__(data, files, save_as_new,
- prefix, queryset,
- **kwargs)
-
- def _construct_form(self, i, **kwargs):
- defaults = {'parent_document': self.parent_document}
-
- # add position argument to the form. Otherwise we will spend
- # a huge amount of time iterating over the list field on form __init__
- emb_list = getattr(self.parent_document,
- self.form._meta.embedded_field)
-
- if emb_list is not None and len(emb_list) > i:
- defaults['position'] = i
- defaults.update(kwargs)
-
- form = super(EmbeddedDocumentFormSet, self)._construct_form(
- i, **defaults)
- return form
-
- @classmethod
- def get_default_prefix(cls):
- return cls.document.__name__.lower()
- @property
- def empty_form(self):
- form = self.form(
- self.parent_document,
- auto_id=self.auto_id,
- prefix=self.add_prefix('__prefix__'),
- empty_permitted=True,
- )
- self.add_fields(form, None)
- return form
-
- def save(self, commit=True):
- # Don't try to save the new documents. Embedded objects don't have
- # a save method anyway.
- objs = super(EmbeddedDocumentFormSet, self).save(commit=False)
- objs = objs or []
-
- if commit and self.parent_document is not None:
- field = self.parent_document._fields.get(self.form._meta.embedded_field, None)
- if isinstance(field, EmbeddedDocumentField):
- try:
- obj = objs[0]
- except IndexError:
- obj = None
- setattr(self.parent_document, self.form._meta.embedded_field, obj)
- else:
- setattr(self.parent_document, self.form._meta.embedded_field, objs)
- self.parent_document.save()
-
- return objs
-
- def _get_embedded_field(parent_doc, document, emb_name=None, can_fail=False):
- if emb_name:
- emb_fields = [f for f in parent_doc._fields.values() if f.name == emb_name]
- if len(emb_fields) == 1:
- field = emb_fields[0]
- if not isinstance(field, (EmbeddedDocumentField, ListField)) or \
- (isinstance(field, EmbeddedDocumentField) and field.document_type != document) or \
- (isinstance(field, ListField) and
- isinstance(field.field, EmbeddedDocumentField) and
- field.field.document_type != document):
- raise Exception("emb_name '%s' is not a EmbeddedDocumentField or not a ListField to %s" % (emb_name, document))
- elif len(emb_fields) == 0:
- raise Exception("%s has no field named '%s'" % (parent_doc, emb_name))
- else:
- emb_fields = [
- f for f in parent_doc._fields.values()
- if (isinstance(field, EmbeddedDocumentField) and field.document_type == document) or \
- (isinstance(field, ListField) and
- isinstance(field.field, EmbeddedDocumentField) and
- field.field.document_type == document)
- ]
- if len(emb_fields) == 1:
- field = emb_fields[0]
- elif len(emb_fields) == 0:
- if can_fail:
- return
- raise Exception("%s has no EmbeddedDocumentField or ListField to %s" % (parent_doc, document))
- else:
- raise Exception("%s has more than 1 EmbeddedDocumentField to %s" % (parent_doc, document))
-
- return field
-
- def embeddedformset_factory(document, parent_document,
- form=EmbeddedDocumentForm,
- formset=EmbeddedDocumentFormSet,
- embedded_name=None,
- fields=None, exclude=None,
- extra=3, can_order=False, can_delete=True,
- max_num=None, formfield_callback=None):
- """
- Returns an ``InlineFormSet`` for the given kwargs.
- You must provide ``fk_name`` if ``model`` has more than one ``ForeignKey``
- to ``parent_model``.
- """
- emb_field = _get_embedded_field(parent_document, document, emb_name=embedded_name)
- if isinstance(emb_field, EmbeddedDocumentField):
- max_num = 1
- kwargs = {
- 'form': form,
- 'formfield_callback': formfield_callback,
- 'formset': formset,
- 'extra': extra,
- 'can_delete': can_delete,
- 'can_order': can_order,
- 'fields': fields,
- 'exclude': exclude,
- 'max_num': max_num,
- }
- FormSet = documentformset_factory(document, **kwargs)
- FormSet.form._meta.embedded_field = emb_field.name
- return FormSet
|