Hi all,
I've run into many situations during my time using Django where I've wanted to be able to express relations based on some other criteria than foreign key equality. A few examples:
- descendants or children of a node in a tree structure
- saved search terms to search results
- a model containing a date range to timestamped items falling within that date range
Currently to do this kind of thing, you might write a getter which returns a queryset - think for example mptt's get_descendants(). But you don't get any of the nice things a real relation field gives you - you can't use that relationship in filters, you can't select/prefetch_related() or values(), there's no reverse relationship, etc.
I've written a Relationship field[0] that lets you define relations in terms of arbitrary Q filters containing objects of a new type, L. An L is like an F, but represents a field on the "local" or "left" side of the relation, where the Q is filtering against the remote "to" side of the relation. For example, in a materialised path tree, this is how you might express descendants:
class Node(models.Model):
path = models.TextField()
descendants = Relationship(
"self",
Q(path__startswith=L('path'), pk__ne=L('pk')),
multiple=True,
reverse_multiple=True,
related_name='ascendants',
)
Now you can use the descendants field like any other many-to-many field in all the places I mentioned above, but the relationship is based purely on prefix-matching on the path field. You also get an ascendants field on Node, which represents the path back to the root and can be used in the same way.
I think this could make a nice new feature for Django. It would give a usability boost to anyone using MPTT or treebeard, for example. It works OK as a third-party library, but the current implementation relies heavily on undocumented ORM internals, and there are a few features I'd like to implement that are impractical without making some ORM changes.
Thoughts/feedback/questions welcome!
Thanks,
Alex