We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
RSR API - allow more than 1 parameter
## Test plan

The example queries in the doc string of filters.py are good examples of queries that should work and return objects (the domain part needs changing of course). I can help construct other more complex examples if needed. One possibility is to construct queries that return the same result as long as the objects in the result set aren't changed that could then be used in the integration/web tests (I'm not sure about the current status of our testing suite). This could be existing project updates for instance, they are pretty stable I'd say.

To test the select_related and prefetch_related functionality is harder. For that you really need to analyze the queries executed with and without them included on sufficiently complex queries, and I don't know if we have that functionality in place right now. Django-debug-toolbar can tell you how many queries a view generates, and I think you can get it to work with DRF.
## Issue description

Akvo Sites partners are currently in need of the more flexibility when it comes to pulling information to their sites from RSR. 

Currently you cannot filter project updates to be read by 'project_partnerships_organisation' AND 'keywords' (for example).

Use case:
Akvo Site, drydev.org
Akvo Data Feed plugin currently uses the RSR API to read, then display in widgets, all project updates for 'project_partnerships_organisation=415'  
Unfortunately, not all of the projects for  'project_partnerships_organisation=415'   have anything to do with the 'DryDev' project they have the site for.
They would like to filter that data by the keyword used on the projects ie. 'drydev'.

This can currently not be done, the ability to add additional parameters for reading data via the API is required.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `akvo/rest/viewsets.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 
3 # Akvo RSR is covered by the GNU Affero General Public License.
4 # See more details in the license.txt file located at the root folder of the Akvo RSR module.
5 # For additional details on the GNU license please see < http://www.gnu.org/licenses/agpl.html >.
6 
7 from django.db.models.fields.related import ForeignKey, ForeignObject
8 
9 from akvo.rest.models import TastyTokenAuthentication
10 
11 from rest_framework import authentication, filters, permissions, viewsets
12 
13 from .filters import RSRGenericFilterBackend
14 
15 
16 class SafeMethodsPermissions(permissions.DjangoObjectPermissions):
17     """
18     Base class to allow any safe methods ('GET', 'OPTIONS' and 'HEAD') without needing to
19     authenticate.
20     """
21 
22     def has_permission(self, request, view):
23         if request.method in permissions.SAFE_METHODS:
24             return True
25         return super(SafeMethodsPermissions, self).has_permission(request, view)
26 
27 
28 class BaseRSRViewSet(viewsets.ModelViewSet):
29     """
30     Base class used for the view sets for RSR models. Provides unified auth and perms settings.
31     """
32     authentication_classes = (authentication.SessionAuthentication, TastyTokenAuthentication, )
33     permission_classes = (SafeMethodsPermissions, )
34     filter_backends = (filters.OrderingFilter, RSRGenericFilterBackend,)
35     ordering_fields = '__all__'
36 
37     def get_queryset(self):
38 
39         def django_filter_filters(request):
40             """
41             Support emulating the DjangoFilterBackend-based filtering that some views used to have
42             """
43             # query string keys reserved by the RSRGenericFilterBackend
44             qs_params = ['filter', 'exclude', 'select_related', 'prefetch_related', ]
45             # query string keys used by core DRF and OrderingFilter
46             exclude_params = ['limit', 'format', 'page', 'ordering', ]
47             filters = {}
48             for key in request.QUERY_PARAMS.keys():
49                 if key not in qs_params + exclude_params:
50                     filters.update({key: request.QUERY_PARAMS.get(key)})
51             return filters
52 
53         def get_lookups_from_filters(legacy_filters):
54             """
55             Cast the values in DjangoFilterBackend-styled query string filters to correct types to
56             be able to use them in regular queryset-filter() calls
57             """
58             # types of lookups supported by the views using DjangoFilterBackend
59             LEGACY_FIELD_LOOKUPS = ['exact', 'contains', 'icontains', 'gt', 'gte', 'lt',
60                                     'lte', ]
61             query_set_lookups = []
62             for key, value in legacy_filters.items():
63                 parts = key.split('__')
64                 if parts[-1] in LEGACY_FIELD_LOOKUPS:
65                     parts = parts[:-1]
66                     model = queryset.model
67                     for part in parts:
68                         field_object, related_model, direct, m2m = model._meta.get_field_by_name(
69                             part)
70                         if direct:
71                             if issubclass(field_object.__class__, ForeignObject):
72                                 model = field_object.related.parent_model
73                             else:
74                                 value = field_object.to_python(value)
75                                 break
76                         else:
77                             model = related_model
78                 query_set_lookups += [{key: value}]
79             return query_set_lookups
80 
81         queryset = super(BaseRSRViewSet, self).get_queryset()
82 
83         # support for old DjangoFilterBackend-based filtering
84         # find all "old styled" filters
85         legacy_filters = django_filter_filters(self.request)
86         # create lookup dicts from the filters found
87         lookups = get_lookups_from_filters(legacy_filters)
88         for lookup in lookups:
89             queryset = queryset.filter(**lookup)
90 
91         return queryset
92 
93 
94 class PublicProjectViewSet(BaseRSRViewSet):
95     """
96     Only public projects or objects related to public projects will be shown.
97     """
98     # project_relation is the default string for constructing a field lookup to the is_public field
99     # on the related Project. Override this in when the viewset is for a model that doesn't have a
100     # direct FK to Project or the FK field isn't named project. E.g. IndicatorViewSet:
101     #   project_relation = 'result__project__'
102     # The lookup is used to filter out objects associated with private projects, see below.
103     project_relation = 'project__'
104 
105     def get_queryset(self):
106 
107         request = self.request
108         user = request.user
109 
110         queryset = super(PublicProjectViewSet, self).get_queryset()
111 
112         def projects_filter_for_non_privileged_users(user, queryset):
113             # Construct the public projects filter field lookup.
114             project_filter = self.project_relation + 'is_public'
115 
116             # Filter the object list into two querysets;
117             # One where the related Projects are public and one where they are private
118             public_objects = queryset.filter(**{project_filter: True}).distinct()
119             private_objects = queryset.filter(**{project_filter: False}).distinct()
120 
121             # In case of an anonymous user, only return the public objects
122             if user.is_anonymous():
123                 queryset = public_objects
124 
125             # Otherwise, check to which objects the user has (change) permission
126             elif private_objects:
127                 permission = type(private_objects[0])._meta.db_table.replace('_', '.change_')
128                 permitted_obj_pks = []
129 
130                 # Loop through all 'private' objects to see if the user has permission to change
131                 # it. If so add its PK to the list of permitted objects.
132                 for obj in private_objects:
133                     if user.has_perm(permission, obj):
134                         permitted_obj_pks.append(obj.pk)
135 
136                 queryset = public_objects | queryset.filter(pk__in=permitted_obj_pks).distinct()
137 
138             return queryset
139 
140         # filter projects if user is "non-privileged"
141         if user.is_anonymous() or not (user.is_superuser or user.is_admin):
142             queryset = projects_filter_for_non_privileged_users(user, queryset)
143 
144         return queryset
145
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/akvo/rest/viewsets.py b/akvo/rest/viewsets.py
--- a/akvo/rest/viewsets.py
+++ b/akvo/rest/viewsets.py
@@ -42,8 +42,9 @@
             """
             # query string keys reserved by the RSRGenericFilterBackend
             qs_params = ['filter', 'exclude', 'select_related', 'prefetch_related', ]
-            # query string keys used by core DRF and OrderingFilter
-            exclude_params = ['limit', 'format', 'page', 'ordering', ]
+            # query string keys used by core DRF, OrderingFilter and Akvo custom views
+            exclude_params = ['limit', 'format', 'page', 'ordering', 'partner_type', 'sync_owner',
+                              'reporting_org']
             filters = {}
             for key in request.QUERY_PARAMS.keys():
                 if key not in qs_params + exclude_params:

{"golden_diff": "diff --git a/akvo/rest/viewsets.py b/akvo/rest/viewsets.py\n--- a/akvo/rest/viewsets.py\n+++ b/akvo/rest/viewsets.py\n@@ -42,8 +42,9 @@\n             \"\"\"\n             # query string keys reserved by the RSRGenericFilterBackend\n             qs_params = ['filter', 'exclude', 'select_related', 'prefetch_related', ]\n-            # query string keys used by core DRF and OrderingFilter\n-            exclude_params = ['limit', 'format', 'page', 'ordering', ]\n+            # query string keys used by core DRF, OrderingFilter and Akvo custom views\n+            exclude_params = ['limit', 'format', 'page', 'ordering', 'partner_type', 'sync_owner',\n+                              'reporting_org']\n             filters = {}\n             for key in request.QUERY_PARAMS.keys():\n                 if key not in qs_params + exclude_params:\n", "issue": "RSR API - allow more than 1 parameter\n## Test plan\n\nThe example queries in the doc string of filters.py are good examples of queries that should work and return objects (the domain part needs changing of course). I can help construct other more complex examples if needed. One possibility is to construct queries that return the same result as long as the objects in the result set aren't changed that could then be used in the integration/web tests (I'm not sure about the current status of our testing suite). This could be existing project updates for instance, they are pretty stable I'd say.\n\nTo test the select_related and prefetch_related functionality is harder. For that you really need to analyze the queries executed with and without them included on sufficiently complex queries, and I don't know if we have that functionality in place right now. Django-debug-toolbar can tell you how many queries a view generates, and I think you can get it to work with DRF.\n## Issue description\n\nAkvo Sites partners are currently in need of the more flexibility when it comes to pulling information to their sites from RSR. \n\nCurrently you cannot filter project updates to be read by 'project_partnerships_organisation' AND 'keywords' (for example).\n\nUse case:\nAkvo Site, drydev.org\nAkvo Data Feed plugin currently uses the RSR API to read, then display in widgets, all project updates for 'project_partnerships_organisation=415'  \nUnfortunately, not all of the projects for  'project_partnerships_organisation=415'   have anything to do with the 'DryDev' project they have the site for.\nThey would like to filter that data by the keyword used on the projects ie. 'drydev'.\n\nThis can currently not be done, the ability to add additional parameters for reading data via the API is required.\n\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\n\n# Akvo RSR is covered by the GNU Affero General Public License.\n# See more details in the license.txt file located at the root folder of the Akvo RSR module.\n# For additional details on the GNU license please see < http://www.gnu.org/licenses/agpl.html >.\n\nfrom django.db.models.fields.related import ForeignKey, ForeignObject\n\nfrom akvo.rest.models import TastyTokenAuthentication\n\nfrom rest_framework import authentication, filters, permissions, viewsets\n\nfrom .filters import RSRGenericFilterBackend\n\n\nclass SafeMethodsPermissions(permissions.DjangoObjectPermissions):\n    \"\"\"\n    Base class to allow any safe methods ('GET', 'OPTIONS' and 'HEAD') without needing to\n    authenticate.\n    \"\"\"\n\n    def has_permission(self, request, view):\n        if request.method in permissions.SAFE_METHODS:\n            return True\n        return super(SafeMethodsPermissions, self).has_permission(request, view)\n\n\nclass BaseRSRViewSet(viewsets.ModelViewSet):\n    \"\"\"\n    Base class used for the view sets for RSR models. Provides unified auth and perms settings.\n    \"\"\"\n    authentication_classes = (authentication.SessionAuthentication, TastyTokenAuthentication, )\n    permission_classes = (SafeMethodsPermissions, )\n    filter_backends = (filters.OrderingFilter, RSRGenericFilterBackend,)\n    ordering_fields = '__all__'\n\n    def get_queryset(self):\n\n        def django_filter_filters(request):\n            \"\"\"\n            Support emulating the DjangoFilterBackend-based filtering that some views used to have\n            \"\"\"\n            # query string keys reserved by the RSRGenericFilterBackend\n            qs_params = ['filter', 'exclude', 'select_related', 'prefetch_related', ]\n            # query string keys used by core DRF and OrderingFilter\n            exclude_params = ['limit', 'format', 'page', 'ordering', ]\n            filters = {}\n            for key in request.QUERY_PARAMS.keys():\n                if key not in qs_params + exclude_params:\n                    filters.update({key: request.QUERY_PARAMS.get(key)})\n            return filters\n\n        def get_lookups_from_filters(legacy_filters):\n            \"\"\"\n            Cast the values in DjangoFilterBackend-styled query string filters to correct types to\n            be able to use them in regular queryset-filter() calls\n            \"\"\"\n            # types of lookups supported by the views using DjangoFilterBackend\n            LEGACY_FIELD_LOOKUPS = ['exact', 'contains', 'icontains', 'gt', 'gte', 'lt',\n                                    'lte', ]\n            query_set_lookups = []\n            for key, value in legacy_filters.items():\n                parts = key.split('__')\n                if parts[-1] in LEGACY_FIELD_LOOKUPS:\n                    parts = parts[:-1]\n                    model = queryset.model\n                    for part in parts:\n                        field_object, related_model, direct, m2m = model._meta.get_field_by_name(\n                            part)\n                        if direct:\n                            if issubclass(field_object.__class__, ForeignObject):\n                                model = field_object.related.parent_model\n                            else:\n                                value = field_object.to_python(value)\n                                break\n                        else:\n                            model = related_model\n                query_set_lookups += [{key: value}]\n            return query_set_lookups\n\n        queryset = super(BaseRSRViewSet, self).get_queryset()\n\n        # support for old DjangoFilterBackend-based filtering\n        # find all \"old styled\" filters\n        legacy_filters = django_filter_filters(self.request)\n        # create lookup dicts from the filters found\n        lookups = get_lookups_from_filters(legacy_filters)\n        for lookup in lookups:\n            queryset = queryset.filter(**lookup)\n\n        return queryset\n\n\nclass PublicProjectViewSet(BaseRSRViewSet):\n    \"\"\"\n    Only public projects or objects related to public projects will be shown.\n    \"\"\"\n    # project_relation is the default string for constructing a field lookup to the is_public field\n    # on the related Project. Override this in when the viewset is for a model that doesn't have a\n    # direct FK to Project or the FK field isn't named project. E.g. IndicatorViewSet:\n    #   project_relation = 'result__project__'\n    # The lookup is used to filter out objects associated with private projects, see below.\n    project_relation = 'project__'\n\n    def get_queryset(self):\n\n        request = self.request\n        user = request.user\n\n        queryset = super(PublicProjectViewSet, self).get_queryset()\n\n        def projects_filter_for_non_privileged_users(user, queryset):\n            # Construct the public projects filter field lookup.\n            project_filter = self.project_relation + 'is_public'\n\n            # Filter the object list into two querysets;\n            # One where the related Projects are public and one where they are private\n            public_objects = queryset.filter(**{project_filter: True}).distinct()\n            private_objects = queryset.filter(**{project_filter: False}).distinct()\n\n            # In case of an anonymous user, only return the public objects\n            if user.is_anonymous():\n                queryset = public_objects\n\n            # Otherwise, check to which objects the user has (change) permission\n            elif private_objects:\n                permission = type(private_objects[0])._meta.db_table.replace('_', '.change_')\n                permitted_obj_pks = []\n\n                # Loop through all 'private' objects to see if the user has permission to change\n                # it. If so add its PK to the list of permitted objects.\n                for obj in private_objects:\n                    if user.has_perm(permission, obj):\n                        permitted_obj_pks.append(obj.pk)\n\n                queryset = public_objects | queryset.filter(pk__in=permitted_obj_pks).distinct()\n\n            return queryset\n\n        # filter projects if user is \"non-privileged\"\n        if user.is_anonymous() or not (user.is_superuser or user.is_admin):\n            queryset = projects_filter_for_non_privileged_users(user, queryset)\n\n        return queryset\n", "path": "akvo/rest/viewsets.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\n\n# Akvo RSR is covered by the GNU Affero General Public License.\n# See more details in the license.txt file located at the root folder of the Akvo RSR module.\n# For additional details on the GNU license please see < http://www.gnu.org/licenses/agpl.html >.\n\nfrom django.db.models.fields.related import ForeignKey, ForeignObject\n\nfrom akvo.rest.models import TastyTokenAuthentication\n\nfrom rest_framework import authentication, filters, permissions, viewsets\n\nfrom .filters import RSRGenericFilterBackend\n\n\nclass SafeMethodsPermissions(permissions.DjangoObjectPermissions):\n    \"\"\"\n    Base class to allow any safe methods ('GET', 'OPTIONS' and 'HEAD') without needing to\n    authenticate.\n    \"\"\"\n\n    def has_permission(self, request, view):\n        if request.method in permissions.SAFE_METHODS:\n            return True\n        return super(SafeMethodsPermissions, self).has_permission(request, view)\n\n\nclass BaseRSRViewSet(viewsets.ModelViewSet):\n    \"\"\"\n    Base class used for the view sets for RSR models. Provides unified auth and perms settings.\n    \"\"\"\n    authentication_classes = (authentication.SessionAuthentication, TastyTokenAuthentication, )\n    permission_classes = (SafeMethodsPermissions, )\n    filter_backends = (filters.OrderingFilter, RSRGenericFilterBackend,)\n    ordering_fields = '__all__'\n\n    def get_queryset(self):\n\n        def django_filter_filters(request):\n            \"\"\"\n            Support emulating the DjangoFilterBackend-based filtering that some views used to have\n            \"\"\"\n            # query string keys reserved by the RSRGenericFilterBackend\n            qs_params = ['filter', 'exclude', 'select_related', 'prefetch_related', ]\n            # query string keys used by core DRF, OrderingFilter and Akvo custom views\n            exclude_params = ['limit', 'format', 'page', 'ordering', 'partner_type', 'sync_owner',\n                              'reporting_org']\n            filters = {}\n            for key in request.QUERY_PARAMS.keys():\n                if key not in qs_params + exclude_params:\n                    filters.update({key: request.QUERY_PARAMS.get(key)})\n            return filters\n\n        def get_lookups_from_filters(legacy_filters):\n            \"\"\"\n            Cast the values in DjangoFilterBackend-styled query string filters to correct types to\n            be able to use them in regular queryset-filter() calls\n            \"\"\"\n            # types of lookups supported by the views using DjangoFilterBackend\n            LEGACY_FIELD_LOOKUPS = ['exact', 'contains', 'icontains', 'gt', 'gte', 'lt',\n                                    'lte', ]\n            query_set_lookups = []\n            for key, value in legacy_filters.items():\n                parts = key.split('__')\n                if parts[-1] in LEGACY_FIELD_LOOKUPS:\n                    parts = parts[:-1]\n                    model = queryset.model\n                    for part in parts:\n                        field_object, related_model, direct, m2m = model._meta.get_field_by_name(\n                            part)\n                        if direct:\n                            if issubclass(field_object.__class__, ForeignObject):\n                                model = field_object.related.parent_model\n                            else:\n                                value = field_object.to_python(value)\n                                break\n                        else:\n                            model = related_model\n                query_set_lookups += [{key: value}]\n            return query_set_lookups\n\n        queryset = super(BaseRSRViewSet, self).get_queryset()\n\n        # support for old DjangoFilterBackend-based filtering\n        # find all \"old styled\" filters\n        legacy_filters = django_filter_filters(self.request)\n        # create lookup dicts from the filters found\n        lookups = get_lookups_from_filters(legacy_filters)\n        for lookup in lookups:\n            queryset = queryset.filter(**lookup)\n\n        return queryset\n\n\nclass PublicProjectViewSet(BaseRSRViewSet):\n    \"\"\"\n    Only public projects or objects related to public projects will be shown.\n    \"\"\"\n    # project_relation is the default string for constructing a field lookup to the is_public field\n    # on the related Project. Override this in when the viewset is for a model that doesn't have a\n    # direct FK to Project or the FK field isn't named project. E.g. IndicatorViewSet:\n    #   project_relation = 'result__project__'\n    # The lookup is used to filter out objects associated with private projects, see below.\n    project_relation = 'project__'\n\n    def get_queryset(self):\n\n        request = self.request\n        user = request.user\n\n        queryset = super(PublicProjectViewSet, self).get_queryset()\n\n        def projects_filter_for_non_privileged_users(user, queryset):\n            # Construct the public projects filter field lookup.\n            project_filter = self.project_relation + 'is_public'\n\n            # Filter the object list into two querysets;\n            # One where the related Projects are public and one where they are private\n            public_objects = queryset.filter(**{project_filter: True}).distinct()\n            private_objects = queryset.filter(**{project_filter: False}).distinct()\n\n            # In case of an anonymous user, only return the public objects\n            if user.is_anonymous():\n                queryset = public_objects\n\n            # Otherwise, check to which objects the user has (change) permission\n            elif private_objects:\n                permission = type(private_objects[0])._meta.db_table.replace('_', '.change_')\n                permitted_obj_pks = []\n\n                # Loop through all 'private' objects to see if the user has permission to change\n                # it. If so add its PK to the list of permitted objects.\n                for obj in private_objects:\n                    if user.has_perm(permission, obj):\n                        permitted_obj_pks.append(obj.pk)\n\n                queryset = public_objects | queryset.filter(pk__in=permitted_obj_pks).distinct()\n\n            return queryset\n\n        # filter projects if user is \"non-privileged\"\n        if user.is_anonymous() or not (user.is_superuser or user.is_admin):\n            queryset = projects_filter_for_non_privileged_users(user, queryset)\n\n        return queryset\n", "path": "akvo/rest/viewsets.py"}]}