From 4841c0e61670edb112f3718d53605987f29d592b Mon Sep 17 00:00:00 2001 From: Phil Starkey Date: Sun, 15 Nov 2020 19:17:12 +1100 Subject: [PATCH] Automatically create nested R object rules if required This Fixes #22 `R` rules that traverse many-to-one or many-to-many relationships are now broken up automatically within `R.check()` into multiple, chained `R` rules. Tests to confirm both the original nested R syntax, and a the new unnested syntax, have been added. --- bridgekeeper/rule_r_tests.py | 30 ++++++++++++++++++++++++++++++ bridgekeeper/rules.py | 10 +++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/bridgekeeper/rule_r_tests.py b/bridgekeeper/rule_r_tests.py index e73ce2e..2dd7a0c 100644 --- a/bridgekeeper/rule_r_tests.py +++ b/bridgekeeper/rule_r_tests.py @@ -106,6 +106,36 @@ def test_nested_rule_object(): assert shrubbery_nomatch not in qs +def test_nested_one_to_many_rule_object(): + user = UserFactory() + + shrubbery_match = ShrubberyFactory(branch=user.profile.branch) + shrubbery_nomatch = ShrubberyFactory() + branch_profile_check_r = R(branch=R(profile=R(user=lambda user: user))) + + assert branch_profile_check_r.check(user, shrubbery_match) + assert not branch_profile_check_r.check(user, shrubbery_nomatch) + + qs = branch_profile_check_r.filter(user, Shrubbery.objects.all()) + assert shrubbery_match in qs + assert shrubbery_nomatch not in qs + + +def test_unnested_one_to_many_rule_object(): + user = UserFactory() + + shrubbery_match = ShrubberyFactory(branch=user.profile.branch) + shrubbery_nomatch = ShrubberyFactory() + branch_profile_check_r = R(branch__profile__user=lambda user: user) + + assert branch_profile_check_r.check(user, shrubbery_match) + assert not branch_profile_check_r.check(user, shrubbery_nomatch) + + qs = branch_profile_check_r.filter(user, Shrubbery.objects.all()) + assert shrubbery_match in qs + assert shrubbery_nomatch not in qs + + def test_many_relation_to_user(): s1 = StoreFactory() s2 = StoreFactory() diff --git a/bridgekeeper/rules.py b/bridgekeeper/rules.py index ad43bc5..4330ae7 100644 --- a/bridgekeeper/rules.py +++ b/bridgekeeper/rules.py @@ -320,7 +320,15 @@ def check(self, user, instance=None): # Find the appropriate LHS on this object, traversing # foreign keys if necessary. lhs = instance - for key_fragment in key.split("__"): + fragments = key.split("__") + for i, key_fragment in enumerate(fragments): + # Catch a many-to-many or many-to-one traversal and split it + # across multiple Rules + if not hasattr(lhs.__class__, "_meta"): + new_kwargs = {"__".join(fragments[i:]): value} + value = R(**new_kwargs) + break + field = lhs.__class__._meta.get_field(key_fragment,) if isinstance(field, ForeignObjectRel): attr = field.get_accessor_name()