pydanny/django-mongonaut

View on GitHub
mongonaut/forms/forms.py

Summary

Maintainability
A
2 hrs
Test Coverage
# -*- coding: utf-8 -*-

from django.forms import Form
from mongoengine.base import TopLevelDocumentMetaclass
from mongoengine.fields import EmbeddedDocumentField
from mongoengine.fields import ListField

from .form_mixins import MongoModelFormBaseMixin
from .form_utils import has_digit
from .form_utils import make_key
from .widgets import get_widget


class MongoModelForm(MongoModelFormBaseMixin, Form):
    """
    This class will take a model and generate a form for the model.
    Recommended use for this project only.

    Example:

    my_form = MongoModelForm(request.POST, model=self.document_type, instance=self.document).get_form()

    if self.form.is_valid():
        # Do your processing
    """

    def __init__(self, form_post_data=None, *args, **kwargs):
        """
        Overriding init so we can set the post vars like a normal form and generate
        the form the same way Django does.
        """
        kwargs.update({'form_post_data': form_post_data})
        super(MongoModelForm, self).__init__(*args, **kwargs)

    def set_fields(self):
        """Sets existing data to form fields."""

        # Get dictionary map of current model
        if self.is_initialized:
            self.model_map_dict = self.create_document_dictionary(self.model_instance)
        else:
            self.model_map_dict = self.create_document_dictionary(self.model)

        form_field_dict = self.get_form_field_dict(self.model_map_dict)
        self.set_form_fields(form_field_dict)

    def set_post_data(self):
        """
            Need to set form data so that validation on all post data occurs and
            places newly entered form data on the form object.
        """
        self.form.data = self.post_data_dict

        # Specifically adding list field keys to the form so they are included
        # in form.cleaned_data after the call to is_valid
        for field_key, field in self.form.fields.items():
            if has_digit(field_key):
                # We have a list field.
                base_key = make_key(field_key, exclude_last_string=True)

                # Add new key value with field to form fields so validation
                # will work correctly
                for key in self.post_data_dict.keys():
                    if base_key in key:
                        self.form.fields.update({key: field})

    def get_form(self):
        """
        Generate the form for view.
        """
        self.set_fields()
        if self.post_data_dict is not None:
            self.set_post_data()
        return self.form

    def create_doc_dict(self, document, doc_key=None, owner_document=None):
        """
        Generate a dictionary representation of the document.  (no recursion)

        DO NOT CALL DIRECTLY
        """
        # Get doc field for top level documents
        if owner_document:
            doc_field = owner_document._fields.get(doc_key, None) if doc_key else None
        else:
            doc_field = document._fields.get(doc_key, None) if doc_key else None

        # Generate the base fields for the document
        doc_dict = {"_document": document if owner_document is None else owner_document,
                    "_key": document.__class__.__name__.lower() if doc_key is None else doc_key,
                    "_document_field": doc_field}

        if not isinstance(document, TopLevelDocumentMetaclass) and doc_key:
            doc_dict.update({"_field_type": EmbeddedDocumentField})

        for key, field in document._fields.items():
            doc_dict[key] = field

        return doc_dict

    def create_list_dict(self, document, list_field, doc_key):
        """
        Genereates a dictionary representation of the list field. Document
        should be the document the list_field comes from.

        DO NOT CALL DIRECTLY
        """
        list_dict = {"_document": document}

        if isinstance(list_field.field, EmbeddedDocumentField):
            list_dict.update(self.create_document_dictionary(document=list_field.field.document_type_obj,
                                                             owner_document=document))

        # Set the list_dict after it may have been updated
        list_dict.update({"_document_field": list_field.field,
                          "_key": doc_key,
                          "_field_type": ListField,
                          "_widget": get_widget(list_field.field),
                          "_value": getattr(document, doc_key, None)})

        return list_dict

    def create_document_dictionary(self, document, document_key=None,
                                                        owner_document=None):
        """
        Given document generates a dictionary representation of the document.
        Includes the widget for each for each field in the document.
        """
        doc_dict = self.create_doc_dict(document, document_key, owner_document)

        for doc_key, doc_field in doc_dict.items():
            # Base fields should not be evaluated
            if doc_key.startswith("_"):
                continue

            if isinstance(doc_field, ListField):
                doc_dict[doc_key] = self.create_list_dict(document, doc_field, doc_key)

            elif isinstance(doc_field, EmbeddedDocumentField):
                doc_dict[doc_key] = self.create_document_dictionary(doc_dict[doc_key].document_type_obj,
                                                                    doc_key)
            else:
                doc_dict[doc_key] = {"_document": document,
                                     "_key": doc_key,
                                     "_document_field": doc_field,
                                     "_widget": get_widget(doc_dict[doc_key], getattr(doc_field, 'disabled', False))}

        return doc_dict