We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Do not include unconfirmed tables for users with viewer & editor access
## Description
1. GET requests to `/api/db/v0/tables/`, `/api/db/v0/tables/<table_id>` and `common_data.tables` currently include unconfirmed tables for all users.
2. Request to `/api/db/v0/schemas/` and `/api/db/v0/schemas/<schema_id>/` have count for `num_tables` which contains count of all tables including ones that are not confirmed.

Confirmation can currently only be performed by a schema manager or a db manager. Users with viewer and editor access cannot should not be able to see tables that aren't confirmed yet.

The UI currently hides unconfirmed tables for case (1), but this should happen at an API level. The count issue mentioned in (2) however, is currently visible on the UI leading to inconsistent count being shown between DB page and schema page.

## Expected behavior
* Endpoints and common_data that contains tables should not include unconfirmed tables for users with viewer and editor access. i.e. Permission check should take `import_verified` into account. This includes:
  - Table list
  - Table get
  - common_data.tables
* `schema.num_tables` should also take this into account.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `mathesar/api/db/permissions/table.py`
Content:
```
1 from django.db.models import Q
2 from rest_access_policy import AccessPolicy
3 
4 from mathesar.models.users import DatabaseRole, Role, SchemaRole
5 
6 
7 class TableAccessPolicy(AccessPolicy):
8     """
9     Anyone can view Table as long as they have
10     at least a Viewer access to the schema or its database
11     Create Access is restricted to superusers or managers of the schema or the database the table is part of.
12     Only superuser or schema/database manager can delete/modify/update the Table
13     """
14 
15     statements = [
16         {
17             # Restrictions for the create method is done by the Serializers when creating the schema,
18             # As the permissions depend on the database object.
19             'action': [
20                 'list',
21                 'retrieve',
22                 'create',
23                 'type_suggestions',
24                 'dependents',
25                 'ui_dependents',
26                 'joinable_tables',
27             ],
28             'principal': 'authenticated',
29             'effect': 'allow',
30         },
31 
32         {
33             'action': [
34                 'destroy',
35                 'update',
36                 'partial_update',
37                 'split_table',
38                 'move_columns',
39                 'previews',
40                 'existing_import',
41                 'map_imported_columns'
42             ],
43             'principal': 'authenticated',
44             'effect': 'allow',
45             'condition_expression': ['(is_superuser or is_table_manager)']
46         },
47     ]
48 
49     @classmethod
50     def _scope_queryset(cls, request, qs, allowed_roles):
51         if not (request.user.is_superuser or request.user.is_anonymous):
52             permissible_database_role_filter = (
53                 Q(schema__database__database_role__role__in=allowed_roles)
54                 & Q(schema__database__database_role__user=request.user)
55             )
56             permissible_schema_roles_filter = (
57                 Q(schema__schema_role__role__in=allowed_roles) & Q(schema__schema_role__user=request.user)
58             )
59             qs = qs.filter(permissible_database_role_filter | permissible_schema_roles_filter)
60         return qs
61 
62     @classmethod
63     def scope_queryset(cls, request, qs):
64         """
65         Used for scoping the queryset of Serializer RelatedField which reference a Table
66         """
67         allowed_roles = (Role.MANAGER.value,)
68 
69         if request.method.lower() == 'get':
70             allowed_roles = allowed_roles + (Role.EDITOR.value, Role.VIEWER.value)
71         return TableAccessPolicy._scope_queryset(request, qs, allowed_roles)
72 
73     @classmethod
74     def scope_viewset_queryset(cls, request, qs):
75         """
76         Used for scoping queryset of the TableViewSet.
77         It is used for listing all the table the user has Viewer access.
78         Restrictions are then applied based on the request method using the Policy statements.
79         This helps us to throw correct error status code instead of a 404 error code
80         """
81         allowed_roles = (Role.MANAGER.value, Role.EDITOR.value, Role.VIEWER.value)
82         return TableAccessPolicy._scope_queryset(request, qs, allowed_roles)
83 
84     def is_table_manager(self, request, view, action):
85         # Table access control is based on Schema and Database Roles as of now
86         # TODO Include Table Role based access when Table Roles are introduced
87         table = view.get_object()
88         is_schema_manager = SchemaRole.objects.filter(
89             user=request.user,
90             schema=table.schema,
91             role=Role.MANAGER.value
92         ).exists()
93         is_db_manager = DatabaseRole.objects.filter(
94             user=request.user,
95             database=table.schema.database,
96             role=Role.MANAGER.value
97         ).exists()
98         return is_db_manager or is_schema_manager
99
```

Path: `mathesar/api/serializers/schemas.py`
Content:
```
1 from rest_access_policy import PermittedSlugRelatedField
2 from rest_framework import serializers
3 
4 from mathesar.api.db.permissions.database import DatabaseAccessPolicy
5 from mathesar.api.exceptions.mixins import MathesarErrorMessageMixin
6 from mathesar.models.base import Database, Schema
7 
8 
9 class SchemaSerializer(MathesarErrorMessageMixin, serializers.HyperlinkedModelSerializer):
10     name = serializers.CharField()
11     # Restrict access to databases with create access.
12     # Unlike PermittedPkRelatedField this field uses a slug instead of an id
13     # Refer https://rsinger86.github.io/drf-access-policy/policy_reuse/
14     database = PermittedSlugRelatedField(
15         access_policy=DatabaseAccessPolicy,
16         slug_field='name',
17         queryset=Database.current_objects.all()
18     )
19     description = serializers.CharField(
20         required=False, allow_blank=True, default=None, allow_null=True
21     )
22     num_tables = serializers.SerializerMethodField()
23     num_queries = serializers.SerializerMethodField()
24 
25     class Meta:
26         model = Schema
27         fields = [
28             'id', 'name', 'database', 'has_dependents', 'description',
29             'num_tables', 'num_queries'
30         ]
31 
32     def get_num_tables(self, obj):
33         return obj.tables.count()
34 
35     def get_num_queries(self, obj):
36         return sum(t.queries.count() for t in obj.tables.all())
37
```

Path: `mathesar/api/db/viewsets/schemas.py`
Content:
```
1 from django_filters import rest_framework as filters
2 from rest_access_policy import AccessViewSetMixin
3 from rest_framework import status, viewsets
4 from rest_framework.decorators import action
5 from rest_framework.mixins import ListModelMixin, RetrieveModelMixin
6 from rest_framework.response import Response
7 
8 from mathesar.api.db.permissions.schema import SchemaAccessPolicy
9 from mathesar.api.dj_filters import SchemaFilter
10 from mathesar.api.pagination import DefaultLimitOffsetPagination
11 from mathesar.api.serializers.dependents import DependentSerializer, DependentFilterSerializer
12 from mathesar.api.serializers.schemas import SchemaSerializer
13 from mathesar.models.base import Schema
14 from mathesar.utils.schemas import create_schema_and_object
15 from mathesar.api.exceptions.validation_exceptions.exceptions import EditingPublicSchemaIsDisallowed
16 
17 
18 class SchemaViewSet(AccessViewSetMixin, viewsets.GenericViewSet, ListModelMixin, RetrieveModelMixin):
19     serializer_class = SchemaSerializer
20     pagination_class = DefaultLimitOffsetPagination
21     filter_backends = (filters.DjangoFilterBackend,)
22     filterset_class = SchemaFilter
23     access_policy = SchemaAccessPolicy
24 
25     def get_queryset(self):
26         qs = Schema.objects.all().order_by('-created_at')
27         return self.access_policy.scope_viewset_queryset(self.request, qs)
28 
29     def create(self, request):
30         serializer = SchemaSerializer(data=request.data, context={'request': request})
31         serializer.is_valid(raise_exception=True)
32         database_name = serializer.validated_data['database'].name
33         schema = create_schema_and_object(
34             serializer.validated_data['name'],
35             database_name,
36             comment=serializer.validated_data.get('description')
37         )
38         serializer = SchemaSerializer(schema)
39         return Response(serializer.data, status=status.HTTP_201_CREATED)
40 
41     def partial_update(self, request, pk=None):
42         serializer = SchemaSerializer(
43             data=request.data, context={'request': request}, partial=True
44         )
45         serializer.is_valid(raise_exception=True)
46 
47         schema = self.get_object()
48 
49         # We forbid editing the public schema
50         if schema.name == "public":
51             raise EditingPublicSchemaIsDisallowed()
52 
53         schema.update_sa_schema(serializer.validated_data)
54 
55         # Reload the schema to avoid cached properties
56         schema = self.get_object()
57         schema.clear_name_cache()
58         serializer = SchemaSerializer(schema, context={'request': request})
59         return Response(serializer.data)
60 
61     def destroy(self, request, pk=None):
62         schema = self.get_object()
63         schema.delete_sa_schema()
64         return Response(status=status.HTTP_204_NO_CONTENT)
65 
66     @action(methods=['get'], detail=True)
67     def dependents(self, request, pk=None):
68         serializer = DependentFilterSerializer(data=request.GET)
69         serializer.is_valid(raise_exception=True)
70         types_exclude = serializer.validated_data['exclude']
71 
72         schema = self.get_object()
73         serializer = DependentSerializer(schema.get_dependents(types_exclude), many=True, context={'request': request})
74         return Response(serializer.data)
75
```
--- 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/mathesar/api/db/permissions/table.py b/mathesar/api/db/permissions/table.py
--- a/mathesar/api/db/permissions/table.py
+++ b/mathesar/api/db/permissions/table.py
@@ -49,13 +49,38 @@
     @classmethod
     def _scope_queryset(cls, request, qs, allowed_roles):
         if not (request.user.is_superuser or request.user.is_anonymous):
-            permissible_database_role_filter = (
+            db_role_access_filter = (
                 Q(schema__database__database_role__role__in=allowed_roles)
                 & Q(schema__database__database_role__user=request.user)
             )
+            schema_role_access_filter = (
+                Q(schema__schema_role__role__in=allowed_roles)
+                & Q(schema__schema_role__user=request.user)
+            )
+
+            # Filters to check whether user is a db/schema manager
+            is_database_manager = (
+                Q(schema__database__database_role__role='manager')
+                & Q(schema__database__database_role__user=request.user)
+            )
+            is_schema_manager = (
+                Q(schema__schema_role__role='manager')
+                & Q(schema__schema_role__user=request.user)
+            )
+
+            # Filter for confirmed tables
+            cnf_table_filter = (Q(import_verified=True) | Q(import_verified__isnull=True))
+
+            # Filters for the purpose of including/removing unconfirmed tables based on user's role
+            permissible_database_role_filter = (
+                is_database_manager & Q(import_verified=False)
+                | cnf_table_filter & db_role_access_filter
+            )
             permissible_schema_roles_filter = (
-                Q(schema__schema_role__role__in=allowed_roles) & Q(schema__schema_role__user=request.user)
+                is_schema_manager & Q(import_verified=False)
+                | cnf_table_filter & schema_role_access_filter
             )
+
             qs = qs.filter(permissible_database_role_filter | permissible_schema_roles_filter)
         return qs
 
diff --git a/mathesar/api/db/viewsets/schemas.py b/mathesar/api/db/viewsets/schemas.py
--- a/mathesar/api/db/viewsets/schemas.py
+++ b/mathesar/api/db/viewsets/schemas.py
@@ -35,7 +35,7 @@
             database_name,
             comment=serializer.validated_data.get('description')
         )
-        serializer = SchemaSerializer(schema)
+        serializer = SchemaSerializer(schema, context={'request': request})
         return Response(serializer.data, status=status.HTTP_201_CREATED)
 
     def partial_update(self, request, pk=None):
diff --git a/mathesar/api/serializers/schemas.py b/mathesar/api/serializers/schemas.py
--- a/mathesar/api/serializers/schemas.py
+++ b/mathesar/api/serializers/schemas.py
@@ -1,9 +1,11 @@
 from rest_access_policy import PermittedSlugRelatedField
 from rest_framework import serializers
 
+from mathesar.api.db.permissions.table import TableAccessPolicy
+
 from mathesar.api.db.permissions.database import DatabaseAccessPolicy
 from mathesar.api.exceptions.mixins import MathesarErrorMessageMixin
-from mathesar.models.base import Database, Schema
+from mathesar.models.base import Database, Schema, Table
 
 
 class SchemaSerializer(MathesarErrorMessageMixin, serializers.HyperlinkedModelSerializer):
@@ -30,7 +32,9 @@
         ]
 
     def get_num_tables(self, obj):
-        return obj.tables.count()
+        qs = Table.objects.filter(schema=obj)
+        count = TableAccessPolicy.scope_queryset(self.context['request'], qs).count()
+        return count
 
     def get_num_queries(self, obj):
         return sum(t.queries.count() for t in obj.tables.all())

{"golden_diff": "diff --git a/mathesar/api/db/permissions/table.py b/mathesar/api/db/permissions/table.py\n--- a/mathesar/api/db/permissions/table.py\n+++ b/mathesar/api/db/permissions/table.py\n@@ -49,13 +49,38 @@\n     @classmethod\n     def _scope_queryset(cls, request, qs, allowed_roles):\n         if not (request.user.is_superuser or request.user.is_anonymous):\n-            permissible_database_role_filter = (\n+            db_role_access_filter = (\n                 Q(schema__database__database_role__role__in=allowed_roles)\n                 & Q(schema__database__database_role__user=request.user)\n             )\n+            schema_role_access_filter = (\n+                Q(schema__schema_role__role__in=allowed_roles)\n+                & Q(schema__schema_role__user=request.user)\n+            )\n+\n+            # Filters to check whether user is a db/schema manager\n+            is_database_manager = (\n+                Q(schema__database__database_role__role='manager')\n+                & Q(schema__database__database_role__user=request.user)\n+            )\n+            is_schema_manager = (\n+                Q(schema__schema_role__role='manager')\n+                & Q(schema__schema_role__user=request.user)\n+            )\n+\n+            # Filter for confirmed tables\n+            cnf_table_filter = (Q(import_verified=True) | Q(import_verified__isnull=True))\n+\n+            # Filters for the purpose of including/removing unconfirmed tables based on user's role\n+            permissible_database_role_filter = (\n+                is_database_manager & Q(import_verified=False)\n+                | cnf_table_filter & db_role_access_filter\n+            )\n             permissible_schema_roles_filter = (\n-                Q(schema__schema_role__role__in=allowed_roles) & Q(schema__schema_role__user=request.user)\n+                is_schema_manager & Q(import_verified=False)\n+                | cnf_table_filter & schema_role_access_filter\n             )\n+\n             qs = qs.filter(permissible_database_role_filter | permissible_schema_roles_filter)\n         return qs\n \ndiff --git a/mathesar/api/db/viewsets/schemas.py b/mathesar/api/db/viewsets/schemas.py\n--- a/mathesar/api/db/viewsets/schemas.py\n+++ b/mathesar/api/db/viewsets/schemas.py\n@@ -35,7 +35,7 @@\n             database_name,\n             comment=serializer.validated_data.get('description')\n         )\n-        serializer = SchemaSerializer(schema)\n+        serializer = SchemaSerializer(schema, context={'request': request})\n         return Response(serializer.data, status=status.HTTP_201_CREATED)\n \n     def partial_update(self, request, pk=None):\ndiff --git a/mathesar/api/serializers/schemas.py b/mathesar/api/serializers/schemas.py\n--- a/mathesar/api/serializers/schemas.py\n+++ b/mathesar/api/serializers/schemas.py\n@@ -1,9 +1,11 @@\n from rest_access_policy import PermittedSlugRelatedField\n from rest_framework import serializers\n \n+from mathesar.api.db.permissions.table import TableAccessPolicy\n+\n from mathesar.api.db.permissions.database import DatabaseAccessPolicy\n from mathesar.api.exceptions.mixins import MathesarErrorMessageMixin\n-from mathesar.models.base import Database, Schema\n+from mathesar.models.base import Database, Schema, Table\n \n \n class SchemaSerializer(MathesarErrorMessageMixin, serializers.HyperlinkedModelSerializer):\n@@ -30,7 +32,9 @@\n         ]\n \n     def get_num_tables(self, obj):\n-        return obj.tables.count()\n+        qs = Table.objects.filter(schema=obj)\n+        count = TableAccessPolicy.scope_queryset(self.context['request'], qs).count()\n+        return count\n \n     def get_num_queries(self, obj):\n         return sum(t.queries.count() for t in obj.tables.all())\n", "issue": "Do not include unconfirmed tables for users with viewer & editor access\n## Description\r\n1. GET requests to `/api/db/v0/tables/`, `/api/db/v0/tables/<table_id>` and `common_data.tables` currently include unconfirmed tables for all users.\r\n2. Request to `/api/db/v0/schemas/` and `/api/db/v0/schemas/<schema_id>/` have count for `num_tables` which contains count of all tables including ones that are not confirmed.\r\n\r\nConfirmation can currently only be performed by a schema manager or a db manager. Users with viewer and editor access cannot should not be able to see tables that aren't confirmed yet.\r\n\r\nThe UI currently hides unconfirmed tables for case (1), but this should happen at an API level. The count issue mentioned in (2) however, is currently visible on the UI leading to inconsistent count being shown between DB page and schema page.\r\n\r\n## Expected behavior\r\n* Endpoints and common_data that contains tables should not include unconfirmed tables for users with viewer and editor access. i.e. Permission check should take `import_verified` into account. This includes:\r\n  - Table list\r\n  - Table get\r\n  - common_data.tables\r\n* `schema.num_tables` should also take this into account.\n", "before_files": [{"content": "from django.db.models import Q\nfrom rest_access_policy import AccessPolicy\n\nfrom mathesar.models.users import DatabaseRole, Role, SchemaRole\n\n\nclass TableAccessPolicy(AccessPolicy):\n    \"\"\"\n    Anyone can view Table as long as they have\n    at least a Viewer access to the schema or its database\n    Create Access is restricted to superusers or managers of the schema or the database the table is part of.\n    Only superuser or schema/database manager can delete/modify/update the Table\n    \"\"\"\n\n    statements = [\n        {\n            # Restrictions for the create method is done by the Serializers when creating the schema,\n            # As the permissions depend on the database object.\n            'action': [\n                'list',\n                'retrieve',\n                'create',\n                'type_suggestions',\n                'dependents',\n                'ui_dependents',\n                'joinable_tables',\n            ],\n            'principal': 'authenticated',\n            'effect': 'allow',\n        },\n\n        {\n            'action': [\n                'destroy',\n                'update',\n                'partial_update',\n                'split_table',\n                'move_columns',\n                'previews',\n                'existing_import',\n                'map_imported_columns'\n            ],\n            'principal': 'authenticated',\n            'effect': 'allow',\n            'condition_expression': ['(is_superuser or is_table_manager)']\n        },\n    ]\n\n    @classmethod\n    def _scope_queryset(cls, request, qs, allowed_roles):\n        if not (request.user.is_superuser or request.user.is_anonymous):\n            permissible_database_role_filter = (\n                Q(schema__database__database_role__role__in=allowed_roles)\n                & Q(schema__database__database_role__user=request.user)\n            )\n            permissible_schema_roles_filter = (\n                Q(schema__schema_role__role__in=allowed_roles) & Q(schema__schema_role__user=request.user)\n            )\n            qs = qs.filter(permissible_database_role_filter | permissible_schema_roles_filter)\n        return qs\n\n    @classmethod\n    def scope_queryset(cls, request, qs):\n        \"\"\"\n        Used for scoping the queryset of Serializer RelatedField which reference a Table\n        \"\"\"\n        allowed_roles = (Role.MANAGER.value,)\n\n        if request.method.lower() == 'get':\n            allowed_roles = allowed_roles + (Role.EDITOR.value, Role.VIEWER.value)\n        return TableAccessPolicy._scope_queryset(request, qs, allowed_roles)\n\n    @classmethod\n    def scope_viewset_queryset(cls, request, qs):\n        \"\"\"\n        Used for scoping queryset of the TableViewSet.\n        It is used for listing all the table the user has Viewer access.\n        Restrictions are then applied based on the request method using the Policy statements.\n        This helps us to throw correct error status code instead of a 404 error code\n        \"\"\"\n        allowed_roles = (Role.MANAGER.value, Role.EDITOR.value, Role.VIEWER.value)\n        return TableAccessPolicy._scope_queryset(request, qs, allowed_roles)\n\n    def is_table_manager(self, request, view, action):\n        # Table access control is based on Schema and Database Roles as of now\n        # TODO Include Table Role based access when Table Roles are introduced\n        table = view.get_object()\n        is_schema_manager = SchemaRole.objects.filter(\n            user=request.user,\n            schema=table.schema,\n            role=Role.MANAGER.value\n        ).exists()\n        is_db_manager = DatabaseRole.objects.filter(\n            user=request.user,\n            database=table.schema.database,\n            role=Role.MANAGER.value\n        ).exists()\n        return is_db_manager or is_schema_manager\n", "path": "mathesar/api/db/permissions/table.py"}, {"content": "from rest_access_policy import PermittedSlugRelatedField\nfrom rest_framework import serializers\n\nfrom mathesar.api.db.permissions.database import DatabaseAccessPolicy\nfrom mathesar.api.exceptions.mixins import MathesarErrorMessageMixin\nfrom mathesar.models.base import Database, Schema\n\n\nclass SchemaSerializer(MathesarErrorMessageMixin, serializers.HyperlinkedModelSerializer):\n    name = serializers.CharField()\n    # Restrict access to databases with create access.\n    # Unlike PermittedPkRelatedField this field uses a slug instead of an id\n    # Refer https://rsinger86.github.io/drf-access-policy/policy_reuse/\n    database = PermittedSlugRelatedField(\n        access_policy=DatabaseAccessPolicy,\n        slug_field='name',\n        queryset=Database.current_objects.all()\n    )\n    description = serializers.CharField(\n        required=False, allow_blank=True, default=None, allow_null=True\n    )\n    num_tables = serializers.SerializerMethodField()\n    num_queries = serializers.SerializerMethodField()\n\n    class Meta:\n        model = Schema\n        fields = [\n            'id', 'name', 'database', 'has_dependents', 'description',\n            'num_tables', 'num_queries'\n        ]\n\n    def get_num_tables(self, obj):\n        return obj.tables.count()\n\n    def get_num_queries(self, obj):\n        return sum(t.queries.count() for t in obj.tables.all())\n", "path": "mathesar/api/serializers/schemas.py"}, {"content": "from django_filters import rest_framework as filters\nfrom rest_access_policy import AccessViewSetMixin\nfrom rest_framework import status, viewsets\nfrom rest_framework.decorators import action\nfrom rest_framework.mixins import ListModelMixin, RetrieveModelMixin\nfrom rest_framework.response import Response\n\nfrom mathesar.api.db.permissions.schema import SchemaAccessPolicy\nfrom mathesar.api.dj_filters import SchemaFilter\nfrom mathesar.api.pagination import DefaultLimitOffsetPagination\nfrom mathesar.api.serializers.dependents import DependentSerializer, DependentFilterSerializer\nfrom mathesar.api.serializers.schemas import SchemaSerializer\nfrom mathesar.models.base import Schema\nfrom mathesar.utils.schemas import create_schema_and_object\nfrom mathesar.api.exceptions.validation_exceptions.exceptions import EditingPublicSchemaIsDisallowed\n\n\nclass SchemaViewSet(AccessViewSetMixin, viewsets.GenericViewSet, ListModelMixin, RetrieveModelMixin):\n    serializer_class = SchemaSerializer\n    pagination_class = DefaultLimitOffsetPagination\n    filter_backends = (filters.DjangoFilterBackend,)\n    filterset_class = SchemaFilter\n    access_policy = SchemaAccessPolicy\n\n    def get_queryset(self):\n        qs = Schema.objects.all().order_by('-created_at')\n        return self.access_policy.scope_viewset_queryset(self.request, qs)\n\n    def create(self, request):\n        serializer = SchemaSerializer(data=request.data, context={'request': request})\n        serializer.is_valid(raise_exception=True)\n        database_name = serializer.validated_data['database'].name\n        schema = create_schema_and_object(\n            serializer.validated_data['name'],\n            database_name,\n            comment=serializer.validated_data.get('description')\n        )\n        serializer = SchemaSerializer(schema)\n        return Response(serializer.data, status=status.HTTP_201_CREATED)\n\n    def partial_update(self, request, pk=None):\n        serializer = SchemaSerializer(\n            data=request.data, context={'request': request}, partial=True\n        )\n        serializer.is_valid(raise_exception=True)\n\n        schema = self.get_object()\n\n        # We forbid editing the public schema\n        if schema.name == \"public\":\n            raise EditingPublicSchemaIsDisallowed()\n\n        schema.update_sa_schema(serializer.validated_data)\n\n        # Reload the schema to avoid cached properties\n        schema = self.get_object()\n        schema.clear_name_cache()\n        serializer = SchemaSerializer(schema, context={'request': request})\n        return Response(serializer.data)\n\n    def destroy(self, request, pk=None):\n        schema = self.get_object()\n        schema.delete_sa_schema()\n        return Response(status=status.HTTP_204_NO_CONTENT)\n\n    @action(methods=['get'], detail=True)\n    def dependents(self, request, pk=None):\n        serializer = DependentFilterSerializer(data=request.GET)\n        serializer.is_valid(raise_exception=True)\n        types_exclude = serializer.validated_data['exclude']\n\n        schema = self.get_object()\n        serializer = DependentSerializer(schema.get_dependents(types_exclude), many=True, context={'request': request})\n        return Response(serializer.data)\n", "path": "mathesar/api/db/viewsets/schemas.py"}], "after_files": [{"content": "from django.db.models import Q\nfrom rest_access_policy import AccessPolicy\n\nfrom mathesar.models.users import DatabaseRole, Role, SchemaRole\n\n\nclass TableAccessPolicy(AccessPolicy):\n    \"\"\"\n    Anyone can view Table as long as they have\n    at least a Viewer access to the schema or its database\n    Create Access is restricted to superusers or managers of the schema or the database the table is part of.\n    Only superuser or schema/database manager can delete/modify/update the Table\n    \"\"\"\n\n    statements = [\n        {\n            # Restrictions for the create method is done by the Serializers when creating the schema,\n            # As the permissions depend on the database object.\n            'action': [\n                'list',\n                'retrieve',\n                'create',\n                'type_suggestions',\n                'dependents',\n                'ui_dependents',\n                'joinable_tables',\n            ],\n            'principal': 'authenticated',\n            'effect': 'allow',\n        },\n\n        {\n            'action': [\n                'destroy',\n                'update',\n                'partial_update',\n                'split_table',\n                'move_columns',\n                'previews',\n                'existing_import',\n                'map_imported_columns'\n            ],\n            'principal': 'authenticated',\n            'effect': 'allow',\n            'condition_expression': ['(is_superuser or is_table_manager)']\n        },\n    ]\n\n    @classmethod\n    def _scope_queryset(cls, request, qs, allowed_roles):\n        if not (request.user.is_superuser or request.user.is_anonymous):\n            db_role_access_filter = (\n                Q(schema__database__database_role__role__in=allowed_roles)\n                & Q(schema__database__database_role__user=request.user)\n            )\n            schema_role_access_filter = (\n                Q(schema__schema_role__role__in=allowed_roles)\n                & Q(schema__schema_role__user=request.user)\n            )\n\n            # Filters to check whether user is a db/schema manager\n            is_database_manager = (\n                Q(schema__database__database_role__role='manager')\n                & Q(schema__database__database_role__user=request.user)\n            )\n            is_schema_manager = (\n                Q(schema__schema_role__role='manager')\n                & Q(schema__schema_role__user=request.user)\n            )\n\n            # Filter for confirmed tables\n            cnf_table_filter = (Q(import_verified=True) | Q(import_verified__isnull=True))\n\n            # Filters for the purpose of including/removing unconfirmed tables based on user's role\n            permissible_database_role_filter = (\n                is_database_manager & Q(import_verified=False)\n                | cnf_table_filter & db_role_access_filter\n            )\n            permissible_schema_roles_filter = (\n                is_schema_manager & Q(import_verified=False)\n                | cnf_table_filter & schema_role_access_filter\n            )\n\n            qs = qs.filter(permissible_database_role_filter | permissible_schema_roles_filter)\n        return qs\n\n    @classmethod\n    def scope_queryset(cls, request, qs):\n        \"\"\"\n        Used for scoping the queryset of Serializer RelatedField which reference a Table\n        \"\"\"\n        allowed_roles = (Role.MANAGER.value,)\n\n        if request.method.lower() == 'get':\n            allowed_roles = allowed_roles + (Role.EDITOR.value, Role.VIEWER.value)\n        return TableAccessPolicy._scope_queryset(request, qs, allowed_roles)\n\n    @classmethod\n    def scope_viewset_queryset(cls, request, qs):\n        \"\"\"\n        Used for scoping queryset of the TableViewSet.\n        It is used for listing all the table the user has Viewer access.\n        Restrictions are then applied based on the request method using the Policy statements.\n        This helps us to throw correct error status code instead of a 404 error code\n        \"\"\"\n        allowed_roles = (Role.MANAGER.value, Role.EDITOR.value, Role.VIEWER.value)\n        return TableAccessPolicy._scope_queryset(request, qs, allowed_roles)\n\n    def is_table_manager(self, request, view, action):\n        # Table access control is based on Schema and Database Roles as of now\n        # TODO Include Table Role based access when Table Roles are introduced\n        table = view.get_object()\n        is_schema_manager = SchemaRole.objects.filter(\n            user=request.user,\n            schema=table.schema,\n            role=Role.MANAGER.value\n        ).exists()\n        is_db_manager = DatabaseRole.objects.filter(\n            user=request.user,\n            database=table.schema.database,\n            role=Role.MANAGER.value\n        ).exists()\n        return is_db_manager or is_schema_manager\n", "path": "mathesar/api/db/permissions/table.py"}, {"content": "from rest_access_policy import PermittedSlugRelatedField\nfrom rest_framework import serializers\n\nfrom mathesar.api.db.permissions.table import TableAccessPolicy\n\nfrom mathesar.api.db.permissions.database import DatabaseAccessPolicy\nfrom mathesar.api.exceptions.mixins import MathesarErrorMessageMixin\nfrom mathesar.models.base import Database, Schema, Table\n\n\nclass SchemaSerializer(MathesarErrorMessageMixin, serializers.HyperlinkedModelSerializer):\n    name = serializers.CharField()\n    # Restrict access to databases with create access.\n    # Unlike PermittedPkRelatedField this field uses a slug instead of an id\n    # Refer https://rsinger86.github.io/drf-access-policy/policy_reuse/\n    database = PermittedSlugRelatedField(\n        access_policy=DatabaseAccessPolicy,\n        slug_field='name',\n        queryset=Database.current_objects.all()\n    )\n    description = serializers.CharField(\n        required=False, allow_blank=True, default=None, allow_null=True\n    )\n    num_tables = serializers.SerializerMethodField()\n    num_queries = serializers.SerializerMethodField()\n\n    class Meta:\n        model = Schema\n        fields = [\n            'id', 'name', 'database', 'has_dependents', 'description',\n            'num_tables', 'num_queries'\n        ]\n\n    def get_num_tables(self, obj):\n        qs = Table.objects.filter(schema=obj)\n        count = TableAccessPolicy.scope_queryset(self.context['request'], qs).count()\n        return count\n\n    def get_num_queries(self, obj):\n        return sum(t.queries.count() for t in obj.tables.all())\n", "path": "mathesar/api/serializers/schemas.py"}, {"content": "from django_filters import rest_framework as filters\nfrom rest_access_policy import AccessViewSetMixin\nfrom rest_framework import status, viewsets\nfrom rest_framework.decorators import action\nfrom rest_framework.mixins import ListModelMixin, RetrieveModelMixin\nfrom rest_framework.response import Response\n\nfrom mathesar.api.db.permissions.schema import SchemaAccessPolicy\nfrom mathesar.api.dj_filters import SchemaFilter\nfrom mathesar.api.pagination import DefaultLimitOffsetPagination\nfrom mathesar.api.serializers.dependents import DependentSerializer, DependentFilterSerializer\nfrom mathesar.api.serializers.schemas import SchemaSerializer\nfrom mathesar.models.base import Schema\nfrom mathesar.utils.schemas import create_schema_and_object\nfrom mathesar.api.exceptions.validation_exceptions.exceptions import EditingPublicSchemaIsDisallowed\n\n\nclass SchemaViewSet(AccessViewSetMixin, viewsets.GenericViewSet, ListModelMixin, RetrieveModelMixin):\n    serializer_class = SchemaSerializer\n    pagination_class = DefaultLimitOffsetPagination\n    filter_backends = (filters.DjangoFilterBackend,)\n    filterset_class = SchemaFilter\n    access_policy = SchemaAccessPolicy\n\n    def get_queryset(self):\n        qs = Schema.objects.all().order_by('-created_at')\n        return self.access_policy.scope_viewset_queryset(self.request, qs)\n\n    def create(self, request):\n        serializer = SchemaSerializer(data=request.data, context={'request': request})\n        serializer.is_valid(raise_exception=True)\n        database_name = serializer.validated_data['database'].name\n        schema = create_schema_and_object(\n            serializer.validated_data['name'],\n            database_name,\n            comment=serializer.validated_data.get('description')\n        )\n        serializer = SchemaSerializer(schema, context={'request': request})\n        return Response(serializer.data, status=status.HTTP_201_CREATED)\n\n    def partial_update(self, request, pk=None):\n        serializer = SchemaSerializer(\n            data=request.data, context={'request': request}, partial=True\n        )\n        serializer.is_valid(raise_exception=True)\n\n        schema = self.get_object()\n\n        # We forbid editing the public schema\n        if schema.name == \"public\":\n            raise EditingPublicSchemaIsDisallowed()\n\n        schema.update_sa_schema(serializer.validated_data)\n\n        # Reload the schema to avoid cached properties\n        schema = self.get_object()\n        schema.clear_name_cache()\n        serializer = SchemaSerializer(schema, context={'request': request})\n        return Response(serializer.data)\n\n    def destroy(self, request, pk=None):\n        schema = self.get_object()\n        schema.delete_sa_schema()\n        return Response(status=status.HTTP_204_NO_CONTENT)\n\n    @action(methods=['get'], detail=True)\n    def dependents(self, request, pk=None):\n        serializer = DependentFilterSerializer(data=request.GET)\n        serializer.is_valid(raise_exception=True)\n        types_exclude = serializer.validated_data['exclude']\n\n        schema = self.get_object()\n        serializer = DependentSerializer(schema.get_dependents(types_exclude), many=True, context={'request': request})\n        return Response(serializer.data)\n", "path": "mathesar/api/db/viewsets/schemas.py"}]}