text: from functools import partial
  
  try:
      from functools import partialmethod
  except ImportError:
      from django.utils.functional import curry as partialmethod
  
  from django import forms
  from django.forms.formsets import all_valid
  from django.urls import reverse
  from django.contrib.admin.exceptions import DisallowedModelAdminToField
  from django.contrib.admin import widgets, helpers
  from django.contrib.admin.utils import (
      unquote, flatten_fieldsets, get_deleted_objects,
  )
  from django.contrib.admin.options import (
      TO_FIELD_VAR, IS_POPUP_VAR,
      get_ul_class, csrf_protect_m,
  )
  import six
  from django.utils.html import escape
  from django.core.exceptions import PermissionDenied
  try:
      from django.db.models.related import RelatedObject
  except ImportError:
      from django.db.models.fields.related import ForeignObjectRel as RelatedObject # noqa
  from django.http import Http404
  from django.template.response import TemplateResponse
  from django.utils.text import capfirst
  from django.utils.translation import ugettext as _
  from django.forms.forms import pretty_name
  from django.forms.models import modelform_defines_fields
  from django.conf import settings
  from django.apps import apps
  
  from django_mongoengine.utils import force_text
  from django_mongoengine.fields import (ListField, EmbeddedDocumentField,
                                         ReferenceField, StringField)
  
  from django_mongoengine.mongo_admin.util import RelationWrapper
  from django_mongoengine.paginator import Paginator
  from django_mongoengine.utils.wrappers import copy_class
  from django_mongoengine.utils.monkey import get_patched_django_module
  from django_mongoengine.forms.documents import (
      DocumentForm, documentform_factory, documentformset_factory,
      inlineformset_factory, BaseInlineDocumentFormSet)
  
  
  def get_content_type_for_model(obj):
      return apps.get_model("contenttypes.ContentType")()
  
  
  djmod = get_patched_django_module(
      "django.contrib.admin.options",
      get_content_type_for_model=get_content_type_for_model,
  )
  
  
  class BaseDocumentAdmin(djmod.ModelAdmin):
      """Functionality common to both ModelAdmin and InlineAdmin."""
      form = DocumentForm
      paginator = Paginator
  
      def formfield_for_dbfield(self, db_field, **kwargs):
          """
          Hook for specifying the form Field instance for a given database Field
          instance.
          If kwargs are given, they're passed to the form Field's constructor.
          """
          request = kwargs.pop("request", None)
  
          # If the field specifies choices, we don't need to look for special
          # admin widgets - we just need to use a select widget of some kind.
          if db_field.choices is not None:
              return self.formfield_for_choice_field(db_field, request, **kwargs)
  
          if isinstance(db_field, ListField) and isinstance(db_field.field, ReferenceField):
              return self.formfield_for_manytomany(db_field, request, **kwargs)
  
          # handle RelatedFields
          if isinstance(db_field, ReferenceField):
              # For non-raw_id fields, wrap the widget with a wrapper that adds
              # extra HTML -- the "add other" interface -- to the end of the
              # rendered output. formfield can be None if it came from a
              # OneToOneField with parent_link=True or a M2M intermediary.
              form_field = db_field.formfield(**kwargs)
              if db_field.name not in self.raw_id_fields:
                  related_modeladmin = self.admin_site._registry.get(db_field.document_type)
                  can_add_related = bool(
                      related_modeladmin and related_modeladmin.has_add_permission(request)
                  )
                  form_field.widget = widgets.RelatedFieldWidgetWrapper(
                      form_field.widget, RelationWrapper(db_field.document_type), self.admin_site,
                      can_add_related=can_add_related)
                  return form_field
  
          if isinstance(db_field, StringField):
              if db_field.max_length is None:
                  kwargs = dict({'widget': widgets.AdminTextareaWidget}, **kwargs)
              else:
                  kwargs = dict({'widget': widgets.AdminTextInputWidget}, **kwargs)
              return db_field.formfield(**kwargs)
  
          # If we've got overrides for the formfield defined, use 'em. **kwargs
          # passed to formfield_for_dbfield override the defaults.
          for klass in db_field.__class__.mro():
              if klass in self.formfield_overrides:
                  kwargs = dict(self.formfield_overrides[klass], **kwargs)
                  return db_field.formfield(**kwargs)
  
          # For any other type of field, just call its formfield() method.
          return db_field.formfield(**kwargs)
  
      def formfield_for_choice_field(self, db_field, request=None, **kwargs):
          """
          Get a form Field for a database Field that has declared choices.
          """
          # If the field is named as a radio_field, use a RadioSelect
          if db_field.name in self.radio_fields:
              # Avoid stomping on custom widget/choices arguments.
              if 'widget' not in kwargs:
                  kwargs['widget'] = widgets.AdminRadioSelect(attrs={
                      'class': get_ul_class(self.radio_fields[db_field.name]),
                  })
              if 'choices' not in kwargs:
                  kwargs['choices'] = db_field.get_choices(
                      include_blank=db_field.blank,
                      blank_choice=[('', _('None'))]
                  )
          return db_field.formfield(**kwargs)
  
      def formfield_for_manytomany(self, db_field, request=None, **kwargs):
          """
          Get a form Field for a ManyToManyField.
          """
          db = kwargs.get('using')
  
          if db_field.name in self.raw_id_fields:
              kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel, using=db)
              kwargs['help_text'] = ''
          elif db_field.name in (list(self.filter_vertical) + list(self.filter_horizontal)):
              kwargs['widget'] = widgets.FilteredSelectMultiple(pretty_name(db_field.name), (db_field.name in self.filter_vertical))
  
          return db_field.formfield(**kwargs)
  
      def get_view_on_site_url(self, obj=None):
          if obj is None or not self.view_on_site:
              return None
  
          if callable(self.view_on_site):
              return self.view_on_site(obj)
          elif self.view_on_site and hasattr(obj, 'get_absolute_url'):
              # use the ContentType lookup if view_on_site is True
              return reverse('admin:view_on_site', kwargs={
                  'content_type_id': 0,
                  'object_id': obj.pk
              })
  
  
  @copy_class(djmod.ModelAdmin)
  class DocumentAdmin(BaseDocumentAdmin):
      "Encapsulates all admin options and functionality for a given model."
      paginator = Paginator
  
      def __init__(self, model, admin_site):
          self.model = model
          self.opts = model._meta
          self.admin_site = admin_site
          super(DocumentAdmin, self).__init__(model, admin_site)
          self.log = not settings.DATABASES.get('default', {}).get(
              'ENGINE', 'django.db.backends.dummy'
          ).endswith('dummy')
          self.change_list_template = 'admin/change_document_list.html'
  
  # XXX: add inline init somewhere
      def _get_inline_instances(self):
          for f in six.itervalues(self.model._fields):
              if not (isinstance(f, ListField) and isinstance(getattr(f, 'field', None), EmbeddedDocumentField)) and not isinstance(f, EmbeddedDocumentField):
                  continue
              # Should only reach here if there is an embedded document...
              if f.name in self.exclude:
                  continue
              document = self.model()
              if hasattr(f, 'field') and f.field is not None:
                  embedded_document = f.field.document_type
              elif hasattr(f, 'document_type'):
                  embedded_document = f.document_type
              else:
                  # For some reason we found an embedded field were either
                  # the field attribute or the field's document type is None.
                  # This shouldn't happen, but apparently does happen:
                  # https://github.com/jschrewe/django-mongoadmin/issues/4
                  # The solution for now is to ignore that field entirely.
                  continue
              inline_admin = EmbeddedStackedDocumentAdmin
              # check if there is an admin for the embedded document in
              # self.inlines. If there is, use this, else use default.
              for inline_class in self.inlines:
                  if inline_class.document == embedded_document:
                      inline_admin = inline_class
              inline_instance = inline_admin(f, document, self.admin_site)
              # if f is an EmbeddedDocumentField set the maximum allowed form instances to one
              if isinstance(f, EmbeddedDocumentField):
                  inline_instance.max_num = 1
                  # exclude field from normal form
                  if f.name not in self.exclude:
                      self.exclude.append(f.name)
              if f.name == 'created_at' and f.name not in self.exclude:
                  self.exclude.append(f.name)
              self.inline_instances.append(inline_instance)
  
      def get_changelist_form(self, request, **kwargs):
          """
          Returns a Form class for use in the Formset on the changelist page.
          """
          defaults = {
              "formfield_callback": partial(self.formfield_for_dbfield, request=request),
          }
          defaults.update(kwargs)
          if defaults.get('fields') is None and not modelform_defines_fields(defaults.get('form')):
              defaults['fields'] = forms.ALL_FIELDS
  
          return documentform_factory(self.model, **defaults)
  
      def get_changelist_formset(self, request, **kwargs):
          """
          Returns a FormSet class for use on the changelist page if list_editable
          is used.
          """
          defaults = {
              "formfield_callback": partial(self.formfield_for_dbfield, request=request),
          }
          defaults.update(kwargs)
          return documentformset_factory(
              self.model, self.get_changelist_form(request), extra=0,
              fields=self.list_editable, **defaults
          )
  
      def get_changelist(self, request, **kwargs):
          """
          Returns the ChangeList class for use on the changelist page.
          """
          from django_mongoengine.mongo_admin.views import DocumentChangeList
          return DocumentChangeList
  
      def log_addition(self, request, object, message):
          """
          Log that an object has been successfully added.
          The default implementation creates an admin LogEntry object.
          """
          if not self.log:
              return
          super(DocumentAdmin, self).log_addition(request, object, message)
  
      def log_change(self, request, object, message):
          """
          Log that an object has been successfully changed.
          The default implementation creates an admin LogEntry object.
          """
          if not self.log:
              return
          super(DocumentAdmin, self).log_change(request, object, message)
  
      def log_deletion(self, request, object, object_repr):
          """
          Log that an object will be deleted. Note that this method is called
          before the deletion.
          The default implementation creates an admin LogEntry object.
          """
          if not self.log:
              return
          super(DocumentAdmin, self).log_deletion(request, object, object_repr)
  
      @property
      def media(self):
          return djmod.ModelAdmin.media.fget(self)
  
      @csrf_protect_m
      def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
  
          to_field = request.POST.get(TO_FIELD_VAR, request.GET.get(TO_FIELD_VAR))
          if to_field and not self.to_field_allowed(request, to_field):
              raise DisallowedModelAdminToField("The field %s cannot be referenced." % to_field)
  
          model = self.model
          opts = model._meta
  
          if request.method == 'POST' and '_saveasnew' in request.POST:
              object_id = None
  
          add = object_id is None
  
          if add:
              if not self.has_add_permission(request):
                  raise PermissionDenied
              obj = None
  
          else:
              obj = self.get_object(request, unquote(object_id), to_field)
  
              if not self.has_change_permission(request, obj):
                  raise PermissionDenied
  
              if obj is None:
                  raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {
                      'name': force_text(opts.verbose_name), 'key': escape(object_id)})
  
          ModelForm = self.get_form(request, obj)
          if request.method == 'POST':
              form = ModelForm(request.POST, request.FILES, instance=obj)
              if form.is_valid():
                  form_validated = True
                  new_object = self.save_form(request, form, change=not add)
              else:
                  form_validated = False
                  new_object = form.instance
              formsets, inline_instances = self._create_formsets(request, new_object, change=not add)
              if all_valid(formsets) and form_validated:
                  self.save_model(request, new_object, form, not add)
                  self.save_related(request, form, formsets, not add)
                  change_message = self.construct_change_message(request, form, formsets, add)
                  if add:
                      self.log_addition(request, new_object, change_message)
                      return self.response_add(request, new_object)
                  else:
                      self.log_change(request, new_object, change_message)
                      return self.response_change(request, new_object)
              else:
                  form_validated = False
          else:
              if add:
                  initial = self.get_changeform_initial_data(request)
                  form = ModelForm(initial=initial)
                  formsets, inline_instances = self._create_formsets(request, form.instance, change=False)
              else:
                  form = ModelForm(instance=obj)
                  formsets, inline_instances = self._create_formsets(request, obj, change=True)
  
          adminForm = helpers.AdminForm(
              form,
              list(self.get_fieldsets(request, obj)),
              self.get_prepopulated_fields(request, obj),
              self.get_readonly_fields(request, obj),
              model_admin=self)
          media = self.media + adminForm.media
  
          inline_formsets = self.get_inline_formsets(request, formsets, inline_instances, obj)
          for inline_formset in inline_formsets:
              media = media + inline_formset.media
  
          context = dict(
              self.admin_site.each_context(request),
              title=(_('Add %s') if add else _('Change %s')) % force_text(opts.verbose_name),
              adminform=adminForm,
              object_id=object_id,
              original=obj,
              is_popup=(IS_POPUP_VAR in request.POST or
                        IS_POPUP_VAR in request.GET),
              to_field=to_field,
              media=media,
              inline_admin_formsets=inline_formsets,
              errors=helpers.AdminErrorList(form, formsets),
              preserved_filters=self.get_preserved_filters(request),
          )
  
          # Hide the "Save" and "Save and continue" buttons if "Save as New" was
          # previously chosen to prevent the interface from getting confusing.
          if request.method == 'POST' and not form_validated and "_saveasnew" in request.POST:
              context['show_save'] = False
              context['show_save_and_continue'] = False
              # Use the change template instead of the add template.
              add = False
  
          context.update(extra_context or {})
  
          return self.render_change_form(request, context, add=add, change=not add, obj=obj, form_url=form_url)
  
      @csrf_protect_m
      def delete_view(self, request, object_id, extra_context=None):
          "The 'delete' admin view for this model."
          opts = self.model._meta
          app_label = opts.app_label
  
          to_field = request.POST.get(TO_FIELD_VAR, request.GET.get(TO_FIELD_VAR))
          if to_field and not self.to_field_allowed(request, to_field):
              raise DisallowedModelAdminToField("The field %s cannot be referenced." % to_field)
  
          obj = self.get_object(request, unquote(object_id), to_field)
  
          if not self.has_delete_permission(request, obj):
              raise PermissionDenied
  
          if obj is None:
              raise Http404(
                  _('%(name)s object with primary key %(key)r does not exist.') %
                  {'name': force_text(opts.verbose_name), 'key': escape(object_id)}
              )
  
          from django.db import router
          using = router.db_for_write(self.model)
  
          # Populate deleted_objects, a data structure of all related objects that
          # will also be deleted.
          (deleted_objects, model_count, perms_needed, protected) = get_deleted_objects(
              [obj], opts, request.user, self.admin_site, using)
  
          if request.POST:  # The user has already confirmed the deletion.
              if perms_needed:
                  raise PermissionDenied
              obj_display = force_text(obj)
              attr = str(to_field) if to_field else opts.pk.attname
              obj_id = obj.serializable_value(attr)
              self.log_deletion(request, obj, obj_display)
              self.delete_model(request, obj)
  
              return self.response_delete(request, obj_display, obj_id)
  
          object_name = force_text(opts.verbose_name)
  
          if perms_needed or protected:
              title = _("Cannot delete %(name)s") % {"name": object_name}
          else:
              title = _("Are you sure?")
  
          context = dict(
              self.admin_site.each_context(request),
              title=title,
              object_name=object_name,
              object=obj,
              deleted_objects=deleted_objects,
              model_count=dict(model_count).items(),
              perms_lacking=perms_needed,
              protected=protected,
              opts=opts,
              app_label=app_label,
              preserved_filters=self.get_preserved_filters(request),
              is_popup=(IS_POPUP_VAR in request.POST or
                        IS_POPUP_VAR in request.GET),
              to_field=to_field,
          )
          context.update(extra_context or {})
  
          return self.render_delete_form(request, context)
  
      def history_view(self, request, object_id, extra_context=None):
          "The 'history' admin view for this model."
          from django.contrib.admin.models import LogEntry
          # First check if the user can see this history.
          model = self.model
          obj = self.get_object(request, unquote(object_id))
          if obj is None:
              raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {
                  'name': force_text(model._meta.verbose_name),
                  'key': escape(object_id),
              })
  
          if not self.has_change_permission(request, obj):
              raise PermissionDenied
  
          # Then get the history for this object.
          opts = model._meta
          app_label = opts.app_label
          action_list = LogEntry.objects.filter(
              object_id=unquote(object_id),
              content_type=get_content_type_for_model(model)
          ).select_related().order_by('action_time')
  
          context = dict(
              self.admin_site.each_context(request),
              title=_('Change history: %s') % force_text(obj),
              action_list=action_list,
              module_name=capfirst(force_text(opts.verbose_name_plural)),
              object=obj,
              opts=opts,
              preserved_filters=self.get_preserved_filters(request),
          )
          context.update(extra_context or {})
  
          request.current_app = self.admin_site.name
  
          return TemplateResponse(request, self.object_history_template or [
              "admin/%s/%s/object_history.html" % (app_label, opts.model_name),
              "admin/%s/object_history.html" % app_label,
              "admin/object_history.html"
          ], context)
  
  
  class InlineDocumentAdmin(BaseDocumentAdmin):
      """
      Options for inline editing of ``model`` instances.
      Provide ``name`` to specify the attribute name of the ``ForeignKey`` from
      ``model`` to its parent. This is required if ``model`` has more than one
      ``ForeignKey`` to its parent.
      """
      document = None
      fk_name = None
      formset = BaseInlineDocumentFormSet
      extra = 1
      max_num = None
      template = None
      verbose_name = None
      verbose_name_plural = None
      can_delete = True
  
      def __init__(self, parent_document, admin_site):
          self.admin_site = admin_site
          self.parent_document = parent_document
          self.opts = self.model._meta
  
          super(InlineDocumentAdmin, self).__init__()
  
          if self.verbose_name is None:
              self.verbose_name = self.model._meta.verbose_name
  
          if self.verbose_name_plural is None:
              self.verbose_name_plural = self.model._meta.verbose_name_plural
  
      media = djmod.ModelAdmin.media
  
      def get_formset(self, request, obj=None, **kwargs):
          """Returns a BaseInlineFormSet class for use in admin add/change views."""
          if self.declared_fieldsets:
              fields = flatten_fieldsets(self.declared_fieldsets)
          else:
              fields = None
          if self.exclude is None:
              exclude = []
          else:
              exclude = list(self.exclude)
          exclude.extend(kwargs.get("exclude", []))
          exclude.extend(self.get_readonly_fields(request, obj))
          # if exclude is an empty list we use None, since that's the actual
          # default
          exclude = exclude or None
          defaults = {
              "form": self.form,
              "formset": self.formset,
              "fields": fields,
              "exclude": exclude,
              "formfield_callback": partialmethod(self.formfield_for_dbfield, request=request),
              "extra": self.extra,
              "max_num": self.max_num,
              "can_delete": self.can_delete,
          }
          defaults.update(kwargs)
          return inlineformset_factory(self.model, **defaults)
  
      def get_fieldsets(self, request, obj=None):
          if self.declared_fieldsets:
              return self.declared_fieldsets
          form = self.get_formset(request).form
          fields = form.base_fields.keys() + list(self.get_readonly_fields(request, obj))
          return [(None, {'fields': fields})]
  
  
  class EmbeddedDocumentAdmin(InlineDocumentAdmin):
      def __init__(self, field, parent_document, admin_site):
          if hasattr(field, 'field'):
              self.model = field.field.document_type
          else:
              self.model = field.document_type
          self.doc_list = getattr(parent_document, field.name)
          self.field = field
          if not isinstance(self.doc_list, list):
              self.doc_list = []
          self.rel_name = field.name
  
          if self.verbose_name is None:
              self.verbose_name = "Field: %s (Document: %s)" % (capfirst(field.name), self.model._meta.verbose_name)
  
          if self.verbose_name_plural is None:
              self.verbose_name_plural = "Field: %s (Document:  %s)" % (capfirst(field.name), self.model._meta.verbose_name_plural)
  
          super(EmbeddedDocumentAdmin, self).__init__(parent_document, admin_site)
  
      def queryset(self, request):
          if isinstance(self.field, ListField):  # list field
              self.doc_list = getattr(self.parent_document, self.rel_name)
          else:  # embedded field
              emb_doc = getattr(self.parent_document, self.rel_name)
              if emb_doc is None:
                  self.doc_list = []
              else:
                  self.doc_list = [emb_doc]
          return self.doc_list
  
  
  class StackedDocumentInline(InlineDocumentAdmin):
      template = 'admin/edit_inline/stacked.html'
  
  
  class EmbeddedStackedDocumentAdmin(EmbeddedDocumentAdmin):
      template = 'admin/edit_inline/stacked.html'
  
  
  class TabularDocumentInline(InlineDocumentAdmin):
      template = 'admin/edit_inline/tabular.html'
  