Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Comparison lhs / value (gt, gte, lt, lte) #40

Open
weber-s opened this issue Nov 2, 2022 · 0 comments
Open

Comparison lhs / value (gt, gte, lt, lte) #40

weber-s opened this issue Nov 2, 2022 · 0 comments

Comments

@weber-s
Copy link

weber-s commented Nov 2, 2022

Hello!

It would be nice to have comparison and not strict equality between lhs and value. For now, we can do:

my_rule = R(field=5)

but we can not do

my_rule = R(field__lt=5)

and that would be terrific!

This would allow stuff like:

start_date_in_futur = Compare(start_date__gte=lambda _: now().date())

can_edit_stuff = R(status="DRAFT") & start_date_in_futur

perms["app.change_model"] = can_edit_stuff

Are you interested in a PR?

The PR would look like this (adapted from R):

ALLOWED_OPERATOR = ["gt", "gte", "le", "lte"]
OPERATOR_MAP = {
    "gt": "__gt__",
    "gte": "__ge__",
    "lt": "__lt__",
    "lte": "__le__",
}


class Compare(R):
    """
    Django-like query for comparison :

        - `Compare(field__gt=3)`
        - `Compare(field__gte=3)`
        - `Compare(field__lt=3)`
        - `Compare(field__gte=3)`
    """

    def __repr__(self):
        return "R({})".format(
            ", ".join("{}__{}={!r}".format(*k.rsplit("__", 1), v) for k, v in self.kwargs.items())
        )

    def check(self, user, instance=None):
        if instance is None:
            return False

        # This loop exits early, returning False, if any argument
        # doesn't match.
        for key, value in self.kwargs.items():

            # Find the appropriate LHS on this object, traversing
            # foreign keys if necessary.
            lhs = instance
            key, operator = key.rsplit("__", 1)

            if operator not in ALLOWED_OPERATOR:
                raise NotImplementedError("Operator must be in %s" % ALLOWED_OPERATOR)

            for key_fragment in key.split("__"):
                field = lhs.__class__._meta.get_field(
                    key_fragment,
                )
                if isinstance(field, ForeignObjectRel):
                    attr = field.get_accessor_name()
                else:
                    attr = key_fragment
                lhs = getattr(lhs, attr)

            # Compare it against the RHS.
            # Note that the LHS will usually be a value, but in the case
            # of a ManyToMany or the 'other side' of a ForeignKey it
            # will be a RelatedManager. In this case, we need to check
            # if there is at least one model that matches the RHS.

            if isinstance(value, Rule):
                raise NotImplementedError("value can not be a Rule")
            # if isinstance(value, Rule):
            #     if isinstance(lhs, Manager):
            #         if not value.filter(user, lhs.all()).exists():
            #             return False
            #     else:
            #         if not value.check(user, lhs):
            #             return False
            # else:
            resolved_value = value(user) if callable(value) else value
            if isinstance(lhs, Manager):
                raise NotImplementedError("lhs cannot be a Manager")
                # if resolved_value not in lhs.all():
                #     return False
            else:
                if not getattr(lhs, OPERATOR_MAP[operator])(resolved_value):
                    return False

        # Woohoo, everything matches!
        return True
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant