sharepoint-rest-api 0.20.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. sharepoint_rest_api/__init__.py +1 -0
  2. sharepoint_rest_api/admin.py +23 -0
  3. sharepoint_rest_api/apps.py +6 -0
  4. sharepoint_rest_api/builders/__init__.py +0 -0
  5. sharepoint_rest_api/builders/camlquery_builder.py +89 -0
  6. sharepoint_rest_api/builders/querystring_builder.py +51 -0
  7. sharepoint_rest_api/builders/rest_builder.py +90 -0
  8. sharepoint_rest_api/builders/search_request_builder.py +86 -0
  9. sharepoint_rest_api/client.py +214 -0
  10. sharepoint_rest_api/config.py +24 -0
  11. sharepoint_rest_api/filters.py +14 -0
  12. sharepoint_rest_api/graph_client.py +409 -0
  13. sharepoint_rest_api/migrations/0001_initial.py +159 -0
  14. sharepoint_rest_api/migrations/__init__.py +0 -0
  15. sharepoint_rest_api/models.py +83 -0
  16. sharepoint_rest_api/serializers/__init__.py +0 -0
  17. sharepoint_rest_api/serializers/fields.py +71 -0
  18. sharepoint_rest_api/serializers/files.py +10 -0
  19. sharepoint_rest_api/serializers/model_serializers.py +47 -0
  20. sharepoint_rest_api/serializers/sharepoint.py +164 -0
  21. sharepoint_rest_api/urls.py +81 -0
  22. sharepoint_rest_api/utils.py +18 -0
  23. sharepoint_rest_api/version.py +24 -0
  24. sharepoint_rest_api/views/__init__.py +0 -0
  25. sharepoint_rest_api/views/base.py +176 -0
  26. sharepoint_rest_api/views/files.py +33 -0
  27. sharepoint_rest_api/views/graph_based.py +22 -0
  28. sharepoint_rest_api/views/model_views.py +37 -0
  29. sharepoint_rest_api/views/settings_based.py +74 -0
  30. sharepoint_rest_api/views/url_based.py +80 -0
  31. sharepoint_rest_api-0.20.1.dist-info/METADATA +66 -0
  32. sharepoint_rest_api-0.20.1.dist-info/RECORD +33 -0
  33. sharepoint_rest_api-0.20.1.dist-info/WHEEL +4 -0
@@ -0,0 +1 @@
1
+ __version__ = "0.18"
@@ -0,0 +1,23 @@
1
+ from django.contrib import admin
2
+
3
+ from .models import SharePointLibrary, SharePointSite, SharePointTenant
4
+
5
+
6
+ @admin.register(SharePointTenant)
7
+ class SharepointTenantAdmin(admin.ModelAdmin):
8
+ search_fields = ("url",)
9
+ list_display = ("url",)
10
+
11
+
12
+ @admin.register(SharePointSite)
13
+ class SharepointSiteAdmin(admin.ModelAdmin):
14
+ search_fields = ("name",)
15
+ list_display = ("name", "site_type", "tenant")
16
+ list_filter = ("site_type", "tenant")
17
+
18
+
19
+ @admin.register(SharePointLibrary)
20
+ class DocumentLibraryAdmin(admin.ModelAdmin):
21
+ search_fields = ("name",)
22
+ list_display = ("name", "site", "active", "public")
23
+ list_filter = ("active", "public")
@@ -0,0 +1,6 @@
1
+ from django.apps import AppConfig as BaseAppConfig
2
+
3
+
4
+ class AppConfig(BaseAppConfig):
5
+ name = __name__.rpartition(".")[0]
6
+ verbose_name = "SharePoint Rest API"
File without changes
@@ -0,0 +1,89 @@
1
+ import logging
2
+
3
+ from office365.sharepoint.listitems.caml.query import CamlQuery
4
+ from office365.sharepoint.views.scope import ViewScope
5
+
6
+ from sharepoint_rest_api.utils import to_camel
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ def recursive_builder(queries, operator="And"):
12
+ if queries:
13
+ query = queries.pop()
14
+ if len(queries) == 0:
15
+ return query
16
+ if len(queries) == 1:
17
+ last_query = queries.pop()
18
+ return f"<{operator}>" + query + last_query + f"</{operator}>"
19
+ return f"<{operator}>" + query + recursive_builder(queries, operator) + f"</{operator}>"
20
+ return ""
21
+
22
+
23
+ class CamlQueryBuilder:
24
+ """Helper Class to create queries in CamlQuery format."""
25
+
26
+ date_operators = ["Geq", "Gt", "Leq", "Lt"]
27
+ mapping_operator = {
28
+ "gte": "Geq",
29
+ "gt": "Gt",
30
+ "lte": "Leq",
31
+ "lt": "Lt",
32
+ "not": "Neq",
33
+ "contains": "Contains",
34
+ "eq": "Eq",
35
+ }
36
+
37
+ def __init__(self, filters=None, scope=None):
38
+ super().__init__()
39
+ self.scope = scope
40
+ self.filters = {} if filters is None else filters
41
+
42
+ def create_query(self):
43
+ where_condition = ""
44
+
45
+ if self.filters.keys():
46
+ filter_queries = []
47
+ for base_filter_name, filter_value in self.filters.items():
48
+ querystring_operator = base_filter_name.split("__")[-1]
49
+ operator = self.mapping_operator.get(querystring_operator, "Eq")
50
+
51
+ filter_name = to_camel(base_filter_name.split("__")[0])
52
+ if operator in self.date_operators:
53
+ column_type, value = (
54
+ "DateTime",
55
+ f"{filter_value}T00:00:00Z",
56
+ ) # 2016-03-26
57
+ query = (
58
+ f'<{operator}><FieldRef Name="{filter_name}" />'
59
+ f'<Value Type="{column_type}">{value}</Value>'
60
+ f"</{operator}>"
61
+ )
62
+ elif operator == "Contains":
63
+ column_type = "Text"
64
+ query = (
65
+ f'<{operator}><FieldRef Name="{filter_name}" />'
66
+ f'<Value Type="{column_type}">{filter_value}</Value>'
67
+ f"</{operator}>"
68
+ )
69
+ else:
70
+ column_type, values = "Text", filter_value.split(",")
71
+ queries = [
72
+ (
73
+ f'<{operator}><FieldRef Name="{filter_name}" />'
74
+ f'<Value Type="{column_type}">{value}</Value>'
75
+ f"</{operator}>"
76
+ )
77
+ for value in values
78
+ ]
79
+ query = recursive_builder(queries, "Or")
80
+ filter_queries.append(query)
81
+ where_condition = recursive_builder(filter_queries)
82
+ if len(filter_queries) > 1:
83
+ where_condition = f"<And>{where_condition}</And>"
84
+
85
+ scope = f' Scope="{self.scope}"' if self.scope else ""
86
+ return f"<View{scope}><Query><Where>{where_condition}</Where></Query></View>"
87
+
88
+ def get_query(self):
89
+ return CamlQuery.parse(self.create_query(), ViewScope.RecursiveAll)
@@ -0,0 +1,51 @@
1
+ import logging
2
+
3
+ from sharepoint_rest_api.utils import to_camel
4
+
5
+ logger = logging.getLogger(__name__)
6
+
7
+
8
+ class QueryStringBuilder:
9
+ """Helper class to create sharepoint querystring."""
10
+
11
+ date_operators = ["ge", "gt", "le", "lt"]
12
+ mapping_operator = {
13
+ "gte": "ge",
14
+ "gt": "gt",
15
+ "lte": "le",
16
+ "lt": "lt",
17
+ "not": "ne",
18
+ "contains": "substringof",
19
+ }
20
+ search = []
21
+
22
+ def __init__(self, filters=None):
23
+ super().__init__()
24
+ self.filters = {} if filters is None else filters
25
+
26
+ def get_filter_querystring(self):
27
+ filter_queries = []
28
+ for base_filter_name, filter_value in self.filters.items():
29
+ # operator
30
+ querystring_operator = base_filter_name.split("__")[-1]
31
+ operator = self.mapping_operator.get(querystring_operator, "eq")
32
+ # filter
33
+ filter_name = to_camel(base_filter_name.split("__")[0])
34
+ if operator in self.date_operators:
35
+ values = [f"{filter_value}T00:00:00Z"] # 2016-03-26
36
+ query = " or ".join([f"{filter_name} {operator} datetime'{value}'" for value in values])
37
+ elif operator == "substringof":
38
+ values = filter_value.split(",")
39
+ query = " or ".join([f"{operator}('{value}', {filter_name})" for value in values])
40
+
41
+ else:
42
+ values = filter_value.split(",")
43
+ query = " or ".join([f"{filter_name} {operator} '{value}'" for value in values])
44
+ if len(values) > 1:
45
+ query = f"({query})"
46
+ filter_queries.append(query)
47
+ logger.info(query)
48
+ return str(" and ".join(filter_queries))
49
+
50
+ def get_querystring(self):
51
+ return self.get_filter_querystring() or ""
@@ -0,0 +1,90 @@
1
+ import logging
2
+
3
+ from sharepoint_rest_api import config
4
+
5
+ logger = logging.getLogger(__name__)
6
+
7
+ GRAPH_URL = "https://graph.microsoft.com/v1.0"
8
+ GRAPH_SEARCH_URL = "https://graph.microsoft.com/v1.0/search/query"
9
+ GRAPH_SCOPE = ["https://graph.microsoft.com/.default"]
10
+
11
+
12
+ class RestBuilderError(Exception):
13
+ """Exception when communicating with the Graph API."""
14
+
15
+
16
+ class RestBuilder:
17
+ """Builds JSON request bodies for Microsoft Graph API queries.
18
+
19
+ Provides static methods to construct Graph API request payloads,
20
+ including KQL query strings and structured JSON bodies for the
21
+ search and batch endpoints. Does not perform any HTTP calls.
22
+ """
23
+
24
+ @staticmethod
25
+ def build_kql_clause(qs_filter_name, filter_value):
26
+ """Build a single KQL clause from a filter expression."""
27
+ parts = qs_filter_name.split("__")
28
+ raw_name = parts[0]
29
+ operator_key = parts[-1] if len(parts) > 1 else "eq"
30
+
31
+ kql_op_map = {
32
+ "not": "<>",
33
+ "contains": ":",
34
+ "eq": ":",
35
+ "gte": ">=..",
36
+ "gt": ">..",
37
+ "lte": "..<=",
38
+ "lt": "..<",
39
+ "between": "..",
40
+ }
41
+ kql_op = kql_op_map.get(operator_key, ":")
42
+ values = [v.strip() for v in filter_value.split(",")]
43
+
44
+ if operator_key == "not_in":
45
+ return [f'-"{v}"' for v in values]
46
+ if operator_key == "contains":
47
+ return [f'{raw_name}:"{values[0]}*"']
48
+ if kql_op == "<>":
49
+ return [f'{raw_name}<>"{values[0]}"']
50
+ if len(values) == 1:
51
+ return [f'{raw_name}:"{values[0]}"']
52
+ or_parts = [f'"{v}"' for v in values]
53
+ return [f"{raw_name}:({' OR '.join(or_parts)})"]
54
+
55
+ @staticmethod
56
+ def build_kql(search=None, filters=None):
57
+ """Build a KQL query string from already-mapped managed property names.
58
+
59
+ Filters dict keys are managed property names (mapped by caller).
60
+ Supports DRF-style operators (__not, __contains) and multi-value.
61
+ """
62
+ if filters is None:
63
+ filters = {}
64
+
65
+ clauses = []
66
+ for qs_filter_name, filter_value in filters.items():
67
+ clauses.extend(RestBuilder.build_kql_clause(qs_filter_name, filter_value))
68
+
69
+ if not clauses:
70
+ return search or "*"
71
+ if search:
72
+ return f"{search} AND {' AND '.join(clauses)}"
73
+ return " AND ".join(clauses)
74
+
75
+ @staticmethod
76
+ def build_search_request_body(kql, start_row, page_size):
77
+ """Build the JSON POST body for a Graph Search API request."""
78
+ request_body = {
79
+ "entityTypes": ["driveItem"],
80
+ "query": {"queryString": kql},
81
+ "region": config.GRAPH_REGION,
82
+ "from": start_row,
83
+ "size": page_size,
84
+ }
85
+ return {"requests": [request_body]}
86
+
87
+ @staticmethod
88
+ def build_batch_body(requests):
89
+ """Build the JSON POST body for a Graph batch request."""
90
+ return {"requests": requests[:20]}
@@ -0,0 +1,86 @@
1
+ from office365.sharepoint.search.query.sort.sort import Sort
2
+
3
+ from sharepoint_rest_api import config
4
+ from sharepoint_rest_api.utils import to_camel
5
+
6
+
7
+ class SearchRequestBuilder:
8
+ """Helper class to build queries in Keyword Query Language (KQL)."""
9
+
10
+ select = None
11
+
12
+ mapping_operator = {
13
+ "gte": ">=",
14
+ "gt": ">",
15
+ "lte": "<=",
16
+ "lt": "<",
17
+ "not": "<>",
18
+ "not_in": ":",
19
+ "eq": ":",
20
+ "between": "..",
21
+ "contains": "*",
22
+ }
23
+
24
+ def __init__( # noqa
25
+ self,
26
+ search=None,
27
+ filters=None,
28
+ select=None,
29
+ order_by=None,
30
+ source_id=None,
31
+ start_row=None,
32
+ ): # noqa
33
+ self.search = search
34
+ self.filters = {} if filters is None else filters
35
+ self.select = select
36
+ self.order_by = order_by
37
+ self.source_id = source_id
38
+ self.start_row = start_row
39
+
40
+ def get_select_properties(self):
41
+ return self.select
42
+
43
+ def get_order_by(self):
44
+ if self.order_by:
45
+ order = to_camel(self.order_by).split(",")
46
+ return [Sort(item[1:], 1) if item.startswith("-") else Sort(item, 0) for item in order]
47
+ return None
48
+
49
+ def get_query(self):
50
+ filter_queries = []
51
+ if self.filters.keys():
52
+ filter_queries = []
53
+ for qs_filter_name, filter_value in self.filters.items():
54
+ filter_name = qs_filter_name.split("__")[0]
55
+ querystring_operator = qs_filter_name.split("__")[-1]
56
+ operator = self.mapping_operator.get(querystring_operator, ":")
57
+ if operator == "..":
58
+ filter_value_from, filter_value_to = filter_value.split("__")
59
+ query = f"{filter_name}:{filter_value_from}{operator}{filter_value_to}"
60
+ elif operator == "*":
61
+ query = f'{filter_name}:"{filter_value}{operator}"'
62
+ else:
63
+ values = filter_value.split(",")
64
+ if querystring_operator == "not_in":
65
+ filter_values = "(" + " ".join([f'-"{value}"' for value in values]) + ")"
66
+ elif len(values) == 1:
67
+ filter_values = f'"{values[0]}"'
68
+ else:
69
+ filter_values = "(" + " OR ".join([f'"{value}"' for value in values]) + ")"
70
+ query = f"{filter_name}{operator}{filter_values}"
71
+ filter_queries.append(query)
72
+ if not filter_queries:
73
+ return f"{self.search}" if self.search else "*"
74
+ qry = " AND ".join(f"{query}" for query in filter_queries)
75
+ return f"{self.search} {qry}" if self.search else qry
76
+
77
+ def build(self):
78
+ return {
79
+ "query_text": self.get_query(),
80
+ "sort_list": self.get_order_by(),
81
+ "select_properties": self.get_select_properties(),
82
+ "start_row": self.start_row,
83
+ "row_limit": config.SHAREPOINT_PAGE_SIZE,
84
+ "trim_duplicates": False,
85
+ "SourceId": self.source_id,
86
+ }
@@ -0,0 +1,214 @@
1
+ import logging
2
+
3
+ from office365.runtime.auth.client_credential import ClientCredential
4
+ from office365.runtime.auth.user_credential import UserCredential
5
+ from office365.sharepoint.client_context import ClientContext
6
+ from office365.sharepoint.files.creation_information import FileCreationInformation
7
+ from office365.sharepoint.files.file import File
8
+ from office365.sharepoint.search.service import SearchService
9
+
10
+ from sharepoint_rest_api import config
11
+ from sharepoint_rest_api.builders.camlquery_builder import CamlQueryBuilder
12
+ from sharepoint_rest_api.builders.querystring_builder import QueryStringBuilder
13
+ from sharepoint_rest_api.builders.search_request_builder import SearchRequestBuilder
14
+ from sharepoint_rest_api.config import SHAREPOINT_PAGE_SIZE
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ default_select = ["*", "FileLeafRef"]
20
+
21
+
22
+ class SharePointClientException(BaseException):
23
+ """SharePoint Exception when initializing the client."""
24
+
25
+
26
+ class SharePointClient:
27
+ """Client to access SharePoint Document Library."""
28
+
29
+ def __init__(self, *args, **kwargs) -> None:
30
+ self.relative_url = kwargs.get("relative_url")
31
+ self.site_path = kwargs.get("url", config.SHAREPOINT_TENANT)
32
+ if config.SHAREPOINT_CONNECTION == "app":
33
+ client_id = kwargs.get("client_id", config.SHAREPOINT_CLIENT_ID)
34
+ client_secret = kwargs.get("client_secret", config.SHAREPOINT_CLIENT_SECRET)
35
+ credentials = ClientCredential(client_id, client_secret)
36
+ elif config.SHAREPOINT_CONNECTION == "user":
37
+ username = kwargs.get("username", config.SHAREPOINT_USERNAME)
38
+ password = kwargs.get("password", config.SHAREPOINT_PASSWORD)
39
+ credentials = UserCredential(username, password)
40
+ else:
41
+ raise SharePointClientException("Invalid connection type")
42
+ self.folder = kwargs.get("folder", "Documents")
43
+ self.context = ClientContext(self.site_path).with_credentials(credentials)
44
+
45
+ def __reduce__(self):
46
+ return SharePointClient, (
47
+ self.relative_url,
48
+ self.site_path,
49
+ self.folder,
50
+ )
51
+
52
+ def get_folder(self, folder_name):
53
+ """Return folder object.
54
+
55
+ :param folder_name: name of the folder
56
+ :return: folder object
57
+ """
58
+ list_obj = self.context.web.lists.get_by_title(folder_name)
59
+ folder = list_obj.root_folder
60
+ self.context.load(folder)
61
+ self.context.execute_query()
62
+ logger.info(f"List url: {folder.properties['ServerRelativeUrl']}")
63
+ return folder
64
+
65
+ def read_folders(self, folder_name):
66
+ """Return folders metadata for subfolder of current folder.
67
+
68
+ :param folder_name:
69
+ :return: folders
70
+ """
71
+ self.get_folder(folder_name)
72
+ folders = self.context.web.folders
73
+ self.context.load(folders)
74
+ self.context.execute_query()
75
+ for folder in folders:
76
+ logger.info(f"Folder name: {folder.properties['Name']}")
77
+ return folders
78
+
79
+ def read_files(self, filters=None):
80
+ """Return files metadata for files in current folder.
81
+
82
+ :param filters:
83
+ :return: files
84
+ """
85
+ filters = {} if filters is None else filters
86
+ querystring = QueryStringBuilder(filters).get_querystring()
87
+ folder = self.get_folder(self.folder)
88
+ files = folder.files.filter(querystring)
89
+ self.context.load(files)
90
+ self.context.execute_query()
91
+ for cur_file in files:
92
+ logger.info(f"File name: {cur_file.properties['Name']}")
93
+
94
+ return files
95
+
96
+ def read_items(self, filters=None, select=None):
97
+ """Retrieve (filtered) items on current folder using querystring.
98
+
99
+ :param filters: filter dictionary
100
+ :param scope: SharePoint scope
101
+ :return: items
102
+ """
103
+ filters = {} if filters is None else filters
104
+ querystring = QueryStringBuilder(filters).get_querystring()
105
+ list_object = self.context.web.lists.get_by_title(self.folder)
106
+ select_string = select or default_select
107
+ items = list_object.get_items().filter(querystring).select(select_string)
108
+ self.context.load(items)
109
+ self.context.execute_query()
110
+ return items
111
+
112
+ def read_file(self, filename):
113
+ """Retrieve file in current folder.
114
+
115
+ :param filename: filename
116
+ :return:
117
+ """
118
+ folder = self.get_folder(self.folder)
119
+ cur_file = folder.files.get_by_url(f"/{self.relative_url}/{self.folder}/{filename}")
120
+ self.context.load(cur_file)
121
+ self.context.execute_query()
122
+ logger.info(f"File name: {cur_file.properties['Name']}")
123
+ return cur_file
124
+
125
+ def read_caml_items(self, filters=None, scope=None):
126
+ """Retrieve (filtered) items on current folder using CamlQueries.
127
+
128
+ :param filters: filter dictionary
129
+ :param scope: SharePoint scope
130
+ :return: items
131
+ """
132
+ filters = {} if filters is None else filters
133
+ list_obj = self.context.web.lists.get_by_title(self.folder)
134
+ qry = CamlQueryBuilder(filters, scope).get_query()
135
+ items = list_obj.get_items(qry)
136
+ self.context.execute_query()
137
+ return items
138
+
139
+ def search( # noqa
140
+ self,
141
+ search=None,
142
+ filters=None,
143
+ select=None,
144
+ order_by=None,
145
+ source_id=None,
146
+ page=1,
147
+ ):
148
+ """Search file in the SharePoint site.
149
+
150
+ :param filter: filter dictionary
151
+ :param select: select string
152
+ :param order_by: SharePoint order fields
153
+ :param source_id: SharePoint SourceId
154
+ :return: items and total row number
155
+ """
156
+ filters = {} if filters is None else filters
157
+ search_service = SearchService(self.context)
158
+ query = SearchRequestBuilder(
159
+ search,
160
+ filters,
161
+ select,
162
+ order_by,
163
+ source_id,
164
+ (page - 1) * SHAREPOINT_PAGE_SIZE,
165
+ ).build()
166
+ result = search_service.post_query(**query)
167
+ self.context.execute_query()
168
+ relevant_results = result.value.PrimaryQueryResult.RelevantResults
169
+ results = relevant_results.Table.Rows
170
+ logger.info(f"Retrieved: {relevant_results.TotalRows} results")
171
+ items = [item.Cells for item in results]
172
+ return items, relevant_results.TotalRows
173
+
174
+ def upload_file_alt(self, target_folder, name, content):
175
+ context = target_folder.context
176
+ info = FileCreationInformation()
177
+ info.content = content
178
+ info.url = name
179
+ info.overwrite = True
180
+ target_file = target_folder.files.add(info)
181
+ context.execute_query()
182
+ return target_file
183
+
184
+ def upload_file(self, file, folder_name="Documents", metadata=None):
185
+ """Upload a file to SharePoint.
186
+
187
+ :param path: inmemory file
188
+ :param folder_name: name of the folder
189
+ :param metadata: metadata dictionary
190
+ :return:
191
+ """
192
+ metadata = {} if metadata is None else metadata
193
+ file_content = file.read()
194
+ target_folder = self.context.web.lists.get_by_title(folder_name).root_folder
195
+ target_file = target_folder.upload_file(file.name, file_content).execute_query()
196
+ logger.info(f"File has been uploaded to url: {target_file.serverRelativeUrl}")
197
+ context = target_folder.context
198
+ item = target_file.listItemAllFields
199
+ for key, value in metadata.items():
200
+ item.set_property(name=key, value=value)
201
+ item.update()
202
+ context.execute_query()
203
+
204
+ def download_file(self, filename):
205
+ """Open a file for downloading.
206
+
207
+ :param filename: name of the file to download
208
+ :return:
209
+
210
+ download file in current folder
211
+ """
212
+ response = File.open_binary(self.context, f"/{self.folder}/{filename}")
213
+ with open(f"./data/{filename}", "wb") as local_file:
214
+ local_file.write(response.content)
@@ -0,0 +1,24 @@
1
+ import os
2
+
3
+ from django.conf import settings
4
+
5
+
6
+ def env_or_settings(name, default):
7
+ return os.environ.get(name, getattr(settings, name, default))
8
+
9
+
10
+ SHAREPOINT_TENANT = env_or_settings("SHAREPOINT_TENANT", "https://unitst.sharepoint.com")
11
+ SHAREPOINT_SITE = env_or_settings("SHAREPOINT_SITE", "GLB-DRP")
12
+ SHAREPOINT_SITE_TYPE = env_or_settings("SHAREPOINT_SITE_TYPE", "sites")
13
+ SHAREPOINT_CONNECTION = env_or_settings("SHAREPOINT_CONNECTION", "app")
14
+ SHAREPOINT_CLIENT_ID = env_or_settings("SHAREPOINT_CLIENT_ID", "invalid_client_id")
15
+ SHAREPOINT_CLIENT_SECRET = env_or_settings("SHAREPOINT_CLIENT_SECRET", "invalid_client_secret")
16
+ SHAREPOINT_USERNAME = env_or_settings("SHAREPOINT_USERNAME", "invalid_username")
17
+ SHAREPOINT_PASSWORD = env_or_settings("SHAREPOINT_PASSWORD", "invalid_password")
18
+ SHAREPOINT_PAGE_SIZE = int(env_or_settings("SHAREPOINT_PAGE_SIZE", 25))
19
+
20
+ GRAPH_CLIENT_ID = env_or_settings("GRAPH_CLIENT_ID", "invalid_graph_client_id")
21
+ GRAPH_CLIENT_SECRET = env_or_settings("GRAPH_CLIENT_SECRET", "invalid_graph_client_secret")
22
+ GRAPH_TENANT = env_or_settings("GRAPH_TENANT", "")
23
+ GRAPH_REGION = env_or_settings("GRAPH_REGION", "US")
24
+ GRAPH_PAGE_SIZE = int(env_or_settings("GRAPH_PAGE_SIZE", 25))
@@ -0,0 +1,14 @@
1
+ from django_filters import rest_framework as filters
2
+
3
+ from sharepoint_rest_api.models import SharePointLibrary
4
+
5
+
6
+ class SharePointLibraryFilter(filters.FilterSet):
7
+ class Meta:
8
+ model = SharePointLibrary
9
+ fields = {
10
+ "site": ["exact", "in"],
11
+ "active": [
12
+ "exact",
13
+ ],
14
+ }