docs: def serialize(self, obj, convert_keys_to_str=False,
                    embed_level=0,
                    encoders=None,
                    autosave=True,
                    for_query=False,path = None):
          """
          Serializes a given object, i.e. converts it to a representation that can be stored in the database.
          This usually involves replacing all `Document` instances by database references to them.
  
          :param obj: The object to serialize.
          :param convert_keys_to_str: If `True`, converts all dictionary keys to string (this is e.g. required for the MongoDB backend)
          :param embed_level: If `embed_level > 0`, instances of `Document` classes will be embedded instead of referenced.
                              The value of the parameter will get decremented by 1 when calling `serialize` on child objects.
          :param autosave: Whether to automatically save embedded objects without a primary key to the database.
          :param for_query: If true, only the `pk` and `__collection__` attributes will be included in document references.
  
          :returns: The serialized object.
          """
  
          if path is None:
              path = []
  
          def get_value(obj,key):
              key_fragments = key.split(".")
              current_dict = obj
              for key_fragment in key_fragments:
                  current_dict = current_dict[key_fragment]
              return current_dict
  
          serialize_with_opts = lambda value,*args,**kwargs : self.serialize(value,*args,
                                                                             encoders = encoders,
                                                                             convert_keys_to_str = convert_keys_to_str,
                                                                             autosave = autosave,
                                                                             for_query = for_query,
                                                                             **kwargs)
  
          if encoders is None:
              encoders = []
  
          for encoder in self.standard_encoders+encoders:
              obj = encoder.encode(obj,path = path)
  
          def encode_as_str(obj):
              if six.PY3:
                  return str(obj)
              else:
                  if isinstance(obj,unicode):
                      return obj
                  elif isinstance(obj,str):
                      return unicode(obj)
                  else:
                      return unicode(str(obj),errors='replace')
  
          if isinstance(obj, dict):
              output_obj = {}
              for key, value in obj.items():
                  new_path = path[:]+[key]
                  try:
                      output_obj[encode_as_str(key) if convert_keys_to_str else key] = serialize_with_opts(value, embed_level=embed_level,path = new_path)
                  except DoNotSerialize:
                      pass
          elif isinstance(obj,six.string_types):
              output_obj = encode_as_str(obj)
          elif isinstance(obj, (list,tuple)):
              try:
                  output_obj = [serialize_with_opts(x, embed_level=embed_level,path = path[:]+[i]) for i,x in enumerate(obj)]
              except DoNotSerialize:
                  pass
          elif isinstance(obj, Document):
              collection = self.get_collection_for_obj(obj)
              if embed_level > 0:
                  try:
                      output_obj = self.serialize(obj, embed_level=embed_level-1)
                  except obj.DoesNotExist:#cannot load object, ignoring...
                      output_obj = self.serialize(obj.lazy_attributes, embed_level=embed_level-1)
                  except DoNotSerialize:
                      pass
              elif obj.embed:
                  output_obj = self.serialize(obj)
              else:
                  if obj.pk == None and autosave:
                      obj.save(self)
  
                  if obj._lazy:
                      # We make sure that all attributes that are already present get included in the reference
                      output_obj = {}
                      if obj.get_pk_name() in output_obj:
                          del output_obj[obj.get_pk_name()]
                      output_obj['pk'] = obj.pk
                      output_obj['__collection__'] = self.classes[obj.__class__]['collection']
                  else:
                      if for_query and not self._allow_documents_in_query:
                          raise ValueError("Documents are not allowed in queries!")
                      if for_query:
                          output_obj = {'$elemMatch' : {'pk':obj.pk,'__collection__':self.classes[obj.__class__]['collection']}}
                      else:
                          ref = "%s:%s" % (self.classes[obj.__class__]['collection'],str(obj.pk))
                          output_obj = {'__ref__' : ref,'pk':obj.pk,'__collection__':self.classes[obj.__class__]['collection']}
  
                  if hasattr(obj,'Meta') and hasattr(obj.Meta,'dbref_includes') and obj.Meta.dbref_includes:
                      for include_key in obj.Meta.dbref_includes:
                          try:
                              value = get_value(obj,include_key)
                              output_obj[include_key.replace(".","_")] = value
                          except KeyError:
                              continue
  
  
          else:
              output_obj = obj
          return output_obj