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

Use the django ORM for filters instead of a custom SQL query generator #116

Open
rmoorman opened this issue Sep 19, 2023 · 0 comments
Open
Assignees

Comments

@rmoorman
Copy link

While trying to use django-scim2 in a project, I noticed, that the default UserFilterQuery uses a custom SQL query building class under the hood. This is also the case for the default GroupFilterParser. It could also very well be that I overlooked something.

Anyway, I tried to work around this by using a custom filter parsers, would like to share what I tried, and could currently see no better option than filing an issue here 🙂.

The custom parsers look something like this (everything that was needed seemed to be already there in the scim2_filter_parser):

from scim2_filter_parser.lexer import SCIMLexer
from scim2_filter_parser.parser import SCIMParser
from scim2_filter_parser.transpilers.django_q_object import Transpiler as SCIMTranspiler

class CustomFilterParserBase:
    attr_map = {}

    @classmethod
    def search(cls, query, request=None):
        return cls.query_to_qs(query, cls.attr_map)

    @staticmethod
    def query_to_qs(query, attr_map):
        tokens = SCIMLexer().tokenize(query)
        ast = SCIMParser().parse(tokens)
        qs = SCIMTranspiler(attr_map).transpile(ast)
        return qs


class UserFilterParser(CustomFilterParserBase):
    attr_map = {
        # attr, sub attr, uri
        ("externalId", None, None): "userlicense__external_user_id",
        ("userName", None, None): "username",
        ("name", "familyName", None): "last_name",
        ("familyName", None, None): "last_name",
        ("name", "givenName", None): "first_name",
        ("givenName", None, None): "first_name",
        ("active", None, None): "is_active",
    }


class GroupFilterParser(CustomFilterParserBase):
    attr_map = {
        # attr, sub attr, uri
        ("externalId", None, None): "external_group_id",
        ("displayName", None, None): "name",
        # ("userName", None, None): "username",
        # ("name", "familyName", None): "last_name",
        # ("familyName", None, None): "last_name",
        # ("name", "givenName", None): "first_name",
        # ("givenName", None, None): "first_name",
        # ("active", None, None): "is_active",
    }

I set them in the SCIM_SERVICE_PROVIDER configuration within settings.py like this:

SCIM_SERVICE_PROVIDER = {
    # ...
    "USER_FILTER_PARSER": "my_scim.filters.UserFilterParser",
    "GROUP_FILTER_PARSER": "my_scim.filters.GroupFilterParser",
    # ...
}

And I have a custom UsersView and GroupsView that look something like this:

import json
from django_scim import constants
from django_scim import exceptions as scim_exceptions
from django_scim import views
from scim2_filter_parser.parser import SCIMParserError

class CustomFilterMixin:
    def _search(self, request, query, start, count):
        qs = self.model_cls.objects.all()

        try:
            q = self.__class__.parser_getter().search(query, request)
        except (ValueError, SCIMParserError) as e:
            raise scim_exceptions.BadRequestError(
                "Invalid filter/search query: " + str(e)
            )
        else:
            qs = qs.filter(q)

        extra_filter_kwargs = self.get_extra_filter_kwargs(request)
        qs = qs.filter(**extra_filter_kwargs)

        extra_exclude_kwargs = self.get_extra_exclude_kwargs(request)
        qs = qs.exclude(**extra_exclude_kwargs)

        qs = self.get_queryset_post_processor(request, qs)
        qs = qs.order_by(self.lookup_field)

        return self._build_response(request, qs, *self._page(request))

class UsersView(CustomFilterMixin, views.UsersView):
    pass

class GroupsView(CustomFilterMixin, views.GroupsView):
    pass

I hope that this can be of use and If you have any more question, please let me know.

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

2 participants