Model
Right now I have two PyMODM Models:
class PlaylistTrack(MongoModel):
post = fields.ReferenceField(Post, primary_key=True)
playlist_position = fields.IntegerField() # Uses zero-indexing
class Post(MongoModel):
reddit_post_id = fields.CharField(required=True, primary_key=True)
subreddit = fields.CharField()
exists_in_playlist = fields.BooleanField()
#
# ... some more irrelevant fields
#
As you can see, the PlaylistTrack
Model includes a reference id to the Post
Model.
The Issue
In my code, I want to get all the PlaylistTracks that match some Post field as well as PlaylistTrack field, like such:
# This is psuedocode, not actually valid PyMODM syntax
tracks = PlaylistTrack.objects.raw(
{"post.subreddit": subreddit_name,
"playlist_position": {"$gt": pos_in_spotify, "$lte": new_pos}}
)
PyMODM doesn't let me do this because post isn't an embedded document, but it's a reference field to another post. Upon studying the documentation, I found that the mongodb aggregate()
pipeline coupled with the $lookup
operator will do a left-outer join, allowing me to do something like this instead:
playlisttracks = PlaylistTrack.objects.all().aggregate(
{
"$lookup": {
"from": "post",
"localField": "_id",
"foreignField": "_id",
"as": "playlisttrack_post"
}
},
{
"$match": {
"playlisttrack_post.subreddit": self.subreddit_name,
"playlisttrack_post.exists_in_playlist": True
}
},
{
"$sort": {"playlist_position": pymongo.ASCENDING}
})
The issue with this is that this returns a PyMongo CommandCursor type, not a PyMODM QuerySet type. This means that I can't use the QuerySet class methods (which are more natural for use with PyMODM Models), but rather have to convert everything to PyMongo, which kind of feels like defeating the purpose of using PyMODM and unnatural.
My Workaround
So instead, I'm doing this as a hacky workaround
posts = Post.objects.raw({"$and":
[{"subreddit": self.subreddit_name},
{"exists_in_playlist": True}]})
post_ids = [p.reddit_post_id for p in posts]
# Get all tracks in playlist in order
playlisttracks = PlaylistTrack.objects \
.raw({"_id": {"$in": post_ids}}) \
.order_by([("playlist_position", pymongo.ASCENDING)]
Instead of doing a join with $lookup
, I just find the Post that I want and use that as part of my query.
This ensures that the returned object is a PyMODM QuerySet object.
My Question
Is there a better way to go about this using native PyMODM syntax? Am I misunderstanding how to use PyMongo / PyMODM as they were intended to be used?
I know joins are kind of tricky to work with in NoSQL, but still I feel like there must be a better way than this.