filtrar elementos incrustados de todas las clases en mongoengine

Tengo dos clases en Mongoengine:

class UserPoints(EmbeddedDocument): user = ReferenceField(User, verbose_name='user') points = IntField(verbose_name='points', required=True) def __unicode__(self): return self.points 

Y

  class Local(Document): token = StringField(max_length=250,verbose_name='token_identifier',unique=True) points = ListField(EmbeddedDocumentField(UserPoints),required=False) def __unicode__(self): return self.name 

Si hago algo como: “LP = Local.objects.filter (points__user = user)” Obtuve todos los locales con puntos de usuario de mi usuario. Pero quiero todos los UserPoints de un usuario. ¿Cómo puedo?

Intento también: “lUs = UserPoints.objects.filter (user = user)” pero tengo una matriz vacía.

PD: Hago algo como esto para resolver el problema, pero no es eficiente.

  LDPoints = [] LP = Local.objects.filter(points__user=user) print 'List P: '+str(len(LP)) for local in LP: for points in local.points: if points.user == user: dPoints = parsePoints(points) lDPoints.append(dPoints) 

Agregar a la respuesta original y obtener venerable es que el marco de agregación tiene $filter ahora por algún tiempo, lo que es mucho más limpio que los métodos $map y $setDifference utilizados en la respuesta original.

 Local._get_collection().aggregate([ { "$match": { "points.user": user } }, { "$project": { "token": 1, "points": { "$filter": { "input": "$points", "as": "el", "cond": { "$eq": [ "$$el.user", user ] } } } }} ]) 

Sin embargo, los mismos principios se aplican para obtener “múltiples” coincidencias de una matriz en la colección que usa el método aggregate() del controlador subyacente, como se llama desde _get_collection() .


Original

La respuesta para evitar “filtrar” sus documentos incrustados solo para el “usuario” seleccionado es utilizar el marco de agregación . Esto le permite manipular el “contenido de matriz” en el servidor de base de datos en lugar de filtrar los resultados en el código de su cliente.

La agregación se realiza con los métodos de controlador de pymongo sin formato, pero como Mongoengine se construye sobre este controlador, accedes al objeto de colección sin ._get_collection() de tu clase con el método ._get_collection() :

 Local._get_collection().aggregate([ # Match the documents that have the required user { "$match": { "points.user": user }}, # unwind the embedded array to de-normalize { "$unwind": "$points" }, # Matching now filters the elements { "$match": { "points.user": user }}, # Group back as an array { "$group": { "_id": "$_id", "token": { "$first": "$token" }, "points": { "$push": "$points" } }} ]) 

Si tiene MongoDB 2.6 o superior en su servidor y su combinación de “usuario / puntos” es siempre única, alternativamente puede filtrar sin el ciclo de $unwind|$match|$group usando los operadores $map y $setDifference disponibles allí:

 Local._get_collection().aggregate([ # Match the documents that have the required user { "$match": { "points.user": user }}, # Filter the array in place { "$project": { "token": 1, "points": { "$setDifference": [ { "$map": { "input": "$points", "as": "el", "in": { "$cond": [ { "$eq": [ "$$el.user", user ] }, "$$el", false ] } } }, [false] ] } }} ]) 

En el segundo caso, $cond es un operador ternario que toma una expresión lógica como primer argumento y los valores que se devuelven cuando esa expresión es true o false como otros argumentos. Dentro del $map , cada elemento se prueba para ver si la condición es verdadera, en este caso “es el campo de usuario igual al usuario seleccionado”.

O bien el contenido de esa posición de matriz se devuelve o, de lo contrario, es false . $setDifference toma la matriz resultante y “filtra” los valores falsos, por lo que solo se devuelven los elementos coincidentes.

En el enfoque heredado, el operador de canalización $unwind se utiliza para convertir con eficacia cada elemento de la matriz en su propio documento con todas las demás propiedades principales. Esto le permite aplicar la misma condición de $match , que, a diferencia de la consulta inicial, elimina los documentos que ahora como elementos individuales ya no coinciden con su condición. Siempre desea la primera etapa, ya que no hay ningún punto en el procesamiento de esta combinación $unwind|$match en todos los documentos que pueden no contener su condición coincidente.

La $group grupos hace que todo vuelva a la línea por documento. Usando la opción $first para devolver todos los demás campos que fueron esencialmente duplicados por el $unwind y el operador $push para reconstruir la matriz con los elementos correspondientes.

Entonces, si bien no hay métodos “integrados” para MongoEngine para hacer este tipo de consulta, puede hacerlo de la forma MongoDB accediendo al controlador en bruto.

También tenga en cuenta que si solo esperaba que un elemento coincidiera en cualquier matriz para su “usuario” u otra consulta, también podría usar el formulario de proyección de campo disponible para el controlador sin formato. Pero el método de agregación es necesario para más de un elemento coincidente de la matriz.