diff options
| author | Nicolas Delaby <ticosax@free.fr> | 2017-09-22 17:53:17 +0200 |
|---|---|---|
| committer | Tim Graham <timograham@gmail.com> | 2017-09-22 11:53:17 -0400 |
| commit | 01d440fa1e6b5c62acfa8b3fde43dfa1505f93c6 (patch) | |
| tree | 21b1f96ecd0fca636746595bce50eb46abdde880 /django/db/models/query_utils.py | |
| parent | 3f9d85d95cab228fd881ea952c707022e9e3bdf3 (diff) | |
Fixed #27332 -- Added FilteredRelation API for conditional join (ON clause) support.
Thanks Anssi Kääriäinen for contributing to the patch.
Diffstat (limited to 'django/db/models/query_utils.py')
| -rw-r--r-- | django/db/models/query_utils.py | 43 |
1 files changed, 42 insertions, 1 deletions
diff --git a/django/db/models/query_utils.py b/django/db/models/query_utils.py index e3f6a730d5..8a889264e5 100644 --- a/django/db/models/query_utils.py +++ b/django/db/models/query_utils.py @@ -16,7 +16,7 @@ from django.utils import tree # PathInfo is used when converting lookups (fk__somecol). The contents # describe the relation in Model terms (model Options and Fields for both # sides of the relation. The join_field is the field backing the relation. -PathInfo = namedtuple('PathInfo', 'from_opts to_opts target_fields join_field m2m direct') +PathInfo = namedtuple('PathInfo', 'from_opts to_opts target_fields join_field m2m direct filtered_relation') class InvalidQuery(Exception): @@ -291,3 +291,44 @@ def check_rel_lookup_compatibility(model, target_opts, field): check(target_opts) or (getattr(field, 'primary_key', False) and check(field.model._meta)) ) + + +class FilteredRelation: + """Specify custom filtering in the ON clause of SQL joins.""" + + def __init__(self, relation_name, *, condition=Q()): + if not relation_name: + raise ValueError('relation_name cannot be empty.') + self.relation_name = relation_name + self.alias = None + if not isinstance(condition, Q): + raise ValueError('condition argument must be a Q() instance.') + self.condition = condition + self.path = [] + + def __eq__(self, other): + return ( + isinstance(other, self.__class__) and + self.relation_name == other.relation_name and + self.alias == other.alias and + self.condition == other.condition + ) + + def clone(self): + clone = FilteredRelation(self.relation_name, condition=self.condition) + clone.alias = self.alias + clone.path = self.path[:] + return clone + + def resolve_expression(self, *args, **kwargs): + """ + QuerySet.annotate() only accepts expression-like arguments + (with a resolve_expression() method). + """ + raise NotImplementedError('FilteredRelation.resolve_expression() is unused.') + + def as_sql(self, compiler, connection): + # Resolve the condition in Join.filtered_relation. + query = compiler.query + where = query.build_filtered_relation_q(self.condition, reuse=set(self.path)) + return compiler.compile(where) |
