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.
- sharepoint_rest_api/__init__.py +1 -0
- sharepoint_rest_api/admin.py +23 -0
- sharepoint_rest_api/apps.py +6 -0
- sharepoint_rest_api/builders/__init__.py +0 -0
- sharepoint_rest_api/builders/camlquery_builder.py +89 -0
- sharepoint_rest_api/builders/querystring_builder.py +51 -0
- sharepoint_rest_api/builders/rest_builder.py +90 -0
- sharepoint_rest_api/builders/search_request_builder.py +86 -0
- sharepoint_rest_api/client.py +214 -0
- sharepoint_rest_api/config.py +24 -0
- sharepoint_rest_api/filters.py +14 -0
- sharepoint_rest_api/graph_client.py +409 -0
- sharepoint_rest_api/migrations/0001_initial.py +159 -0
- sharepoint_rest_api/migrations/__init__.py +0 -0
- sharepoint_rest_api/models.py +83 -0
- sharepoint_rest_api/serializers/__init__.py +0 -0
- sharepoint_rest_api/serializers/fields.py +71 -0
- sharepoint_rest_api/serializers/files.py +10 -0
- sharepoint_rest_api/serializers/model_serializers.py +47 -0
- sharepoint_rest_api/serializers/sharepoint.py +164 -0
- sharepoint_rest_api/urls.py +81 -0
- sharepoint_rest_api/utils.py +18 -0
- sharepoint_rest_api/version.py +24 -0
- sharepoint_rest_api/views/__init__.py +0 -0
- sharepoint_rest_api/views/base.py +176 -0
- sharepoint_rest_api/views/files.py +33 -0
- sharepoint_rest_api/views/graph_based.py +22 -0
- sharepoint_rest_api/views/model_views.py +37 -0
- sharepoint_rest_api/views/settings_based.py +74 -0
- sharepoint_rest_api/views/url_based.py +80 -0
- sharepoint_rest_api-0.20.1.dist-info/METADATA +66 -0
- sharepoint_rest_api-0.20.1.dist-info/RECORD +33 -0
- 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")
|
|
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
|
+
}
|