docuware-client 0.5.3__tar.gz
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.
- docuware_client-0.5.3/LICENSE +29 -0
- docuware_client-0.5.3/PKG-INFO +297 -0
- docuware_client-0.5.3/README.md +274 -0
- docuware_client-0.5.3/docuware/__init__.py +9 -0
- docuware_client-0.5.3/docuware/cidict.py +80 -0
- docuware_client-0.5.3/docuware/cijson.py +38 -0
- docuware_client-0.5.3/docuware/cli/__init__.py +0 -0
- docuware_client-0.5.3/docuware/cli/dw.py +312 -0
- docuware_client-0.5.3/docuware/client.py +69 -0
- docuware_client-0.5.3/docuware/conn.py +368 -0
- docuware_client-0.5.3/docuware/dialogs.py +277 -0
- docuware_client-0.5.3/docuware/document.py +104 -0
- docuware_client-0.5.3/docuware/errors.py +49 -0
- docuware_client-0.5.3/docuware/fields.py +97 -0
- docuware_client-0.5.3/docuware/filecabinet.py +189 -0
- docuware_client-0.5.3/docuware/organization.py +82 -0
- docuware_client-0.5.3/docuware/parser.py +218 -0
- docuware_client-0.5.3/docuware/structs.py +138 -0
- docuware_client-0.5.3/docuware/tasks.py +35 -0
- docuware_client-0.5.3/docuware/types.py +342 -0
- docuware_client-0.5.3/docuware/users.py +288 -0
- docuware_client-0.5.3/docuware/utils.py +104 -0
- docuware_client-0.5.3/pyproject.toml +37 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022, Stefan Schönberger <mail@sniner.dev>
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
17
|
+
contributors may be used to endorse or promote products derived from
|
|
18
|
+
this software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: docuware-client
|
|
3
|
+
Version: 0.5.3
|
|
4
|
+
Summary: DocuWare REST-API client
|
|
5
|
+
License: BSD-3-Clause
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Author: Stefan Schönberger
|
|
8
|
+
Author-email: mail@sniner.dev
|
|
9
|
+
Requires-Python: >=3.9
|
|
10
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Requires-Dist: requests (>=2.32.3,<3.0.0)
|
|
20
|
+
Project-URL: Repository, https://github.com/sniner/docuware-client
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# docuware-client
|
|
24
|
+
|
|
25
|
+
This is a client library for the REST API of [DocuWare][1] DMS. Since
|
|
26
|
+
[DocuWare's documentation][2] regarding the REST API is very sparse (at the
|
|
27
|
+
time these lines were written), this client serves only a part of the API's
|
|
28
|
+
functionality.
|
|
29
|
+
|
|
30
|
+
Please keep in mind: **This software is not related to DocuWare.** It is a work
|
|
31
|
+
in progress, may yield unexpected results, and almost certainly contains bugs.
|
|
32
|
+
|
|
33
|
+
> ⚠️ Starting with version 0.5.0, OAuth2 authentication is the new default.
|
|
34
|
+
> Unless you explicitly request cookie authentication with
|
|
35
|
+
> `dw.login(..., cookie_auth=True)`, OAuth2 will be used. OAuth2 authentication
|
|
36
|
+
> has been available since DocuWare 7.10, and
|
|
37
|
+
> [cookie authentication will be discontinued](https://start.docuware.com/blog/product-news/docuware-sdk-discontinuation-of-cookie-authentication)
|
|
38
|
+
> with DocuWare 7.11.
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
## Usage
|
|
42
|
+
|
|
43
|
+
First you have to log in and create a persistent session:
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
import json
|
|
47
|
+
import pathlib
|
|
48
|
+
import docuware
|
|
49
|
+
|
|
50
|
+
dw = docuware.Client("http://localhost")
|
|
51
|
+
session = dw.login("username", "password", "organization")
|
|
52
|
+
with open(".session", "w") as f:
|
|
53
|
+
json.dump(session, f)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
From then on you have to reuse the session, otherwise you will be locked out of
|
|
57
|
+
the DocuWare service for a period of time (10 minutes or even longer). As the
|
|
58
|
+
session cookie may change on subsequent logins, update the session file on
|
|
59
|
+
every login.
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
session_file = pathlib.Path(".session")
|
|
63
|
+
if session_file.exists():
|
|
64
|
+
with open(session_file) as f:
|
|
65
|
+
session = json.load(f)
|
|
66
|
+
else:
|
|
67
|
+
session = None
|
|
68
|
+
dw = docuware.Client("http://localhost")
|
|
69
|
+
session = dw.login("username", "password", "organization", saved_session=session)
|
|
70
|
+
with open(session_file, "w") as f:
|
|
71
|
+
json.dump(session, f)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Iterate over the organizations and file cabinets:
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
for org in dw.organizations:
|
|
78
|
+
print(org)
|
|
79
|
+
for fc in org.file_cabinets:
|
|
80
|
+
print(" ", fc)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
If you already know the ID or name of the objects, you can also access them
|
|
84
|
+
directly.
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
org = dw.organization("1")
|
|
88
|
+
fc = org.file_cabinet("Archive")
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Now some examples of how to search for documents. First you need a search
|
|
92
|
+
dialog:
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
# Let's use the first one:
|
|
96
|
+
dlg = fc.search_dialog()
|
|
97
|
+
# Or a specific search dialog:
|
|
98
|
+
dlg = fc.search_dialog("Default search dialog")
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Each search term consists of a field name and a search pattern. Each search
|
|
102
|
+
dialog knows its fields:
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
for field in dlg.fields.values():
|
|
106
|
+
print("Id =", field.id)
|
|
107
|
+
print("Length=", field.length)
|
|
108
|
+
print("Name =", field.name)
|
|
109
|
+
print("Type =", field.type)
|
|
110
|
+
print("-------")
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Let's search for some documents:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
# Search for DOCNO equal to '123456':
|
|
117
|
+
for result in dlg.search("DOCNO=123456"):
|
|
118
|
+
print(result)
|
|
119
|
+
# Search for two patterns alternatively:
|
|
120
|
+
for result in dlg.search(["DOCNO=123456", "DOCNO=654321"], operation=docuware.OR):
|
|
121
|
+
print(result)
|
|
122
|
+
# Search for documents in a date range (01-31 January 2023):
|
|
123
|
+
for result in dlg.search("DWSTOREDATETIME=2023-01-01T00:00:00,2023-02-01T00:00:00")
|
|
124
|
+
print(result)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Please note that search terms may also contain metacharacters such as `*`, `(`,
|
|
128
|
+
`)`, which may need to be escaped when searching for these characters
|
|
129
|
+
themselves.
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
for result in dlg.search("DOCTYPE=Invoice \\(incoming\\)"):
|
|
133
|
+
print(result)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Search terms can be as simple as a single string, but can also be more complex.
|
|
137
|
+
The following two queries are equivalent:
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
dlg.search(["FIELD1=TERM1,TERM2", "FIELD2=TERM3"])
|
|
141
|
+
dlg.search({"FIELD1": ["TERM1", "TERM2"], "FIELD2": ["TERM3"]})
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The result of a search is always an iterator over the search results, even if
|
|
145
|
+
no result was obtained. Each individual search result holds a `document`
|
|
146
|
+
attribute, which gives access to the document in the archive. The document
|
|
147
|
+
itself can be downloaded as a whole or only individual attachments.
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
for result in dlg.search("DOCNO=123456"):
|
|
151
|
+
doc = result.document
|
|
152
|
+
# Download the complete document ...
|
|
153
|
+
data, content_type, filename = doc.download(keep_annotations=True)
|
|
154
|
+
docuware.write_binary_file(data, filename)
|
|
155
|
+
# ... or individual attachments (or sections, as DocuWare calls them)
|
|
156
|
+
for att in doc.attachments:
|
|
157
|
+
data, content_type, filename = att.download()
|
|
158
|
+
docuware.write_binary_file(data, filename)
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Create data entry in file cabinet:
|
|
162
|
+
```python
|
|
163
|
+
data = {
|
|
164
|
+
"FIELD1": "value1",
|
|
165
|
+
"FIELD2": "value2",
|
|
166
|
+
}
|
|
167
|
+
response = fc.create_data_entry(data)
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
_Subject to rewrite:_ Update data fields of document. The search parameter must
|
|
171
|
+
return a single document. Use a loop to execute this function on multiple
|
|
172
|
+
documents:
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
fields = {
|
|
176
|
+
"FIELD1": "value1",
|
|
177
|
+
"FIELD2": 99999
|
|
178
|
+
}
|
|
179
|
+
response = fc.update_data_entry(["FIELD1=TERM1,TERM2", "FIELD2=TERM3"], user_fields)
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Delete documents:
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
dlg = fc.search_dialog()
|
|
186
|
+
for result in dlg.search(["FIELD1=TERM1,TERM2", "FIELD2=TERM3"]):
|
|
187
|
+
document = result.document
|
|
188
|
+
document.delete()
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Users and groups of an organisation can be accessed and managed:
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
# Iterate over the list of users and groups:
|
|
195
|
+
for user in org.users:
|
|
196
|
+
print(user)
|
|
197
|
+
for group in org.groups:
|
|
198
|
+
print(group)
|
|
199
|
+
|
|
200
|
+
# Find a specific user:
|
|
201
|
+
user = org.users["John Doe"] # or: org.users.get("John Doe")
|
|
202
|
+
|
|
203
|
+
# Add a user to a group:
|
|
204
|
+
group = org.groups["Managers"] # or: org.groups.get("Managers")
|
|
205
|
+
group.add_user(user)
|
|
206
|
+
# or
|
|
207
|
+
user.add_to_group(group)
|
|
208
|
+
|
|
209
|
+
# Deactivate user:
|
|
210
|
+
user.active = False # or True to activate user
|
|
211
|
+
|
|
212
|
+
# Create a new user:
|
|
213
|
+
user = docuware.User(first_name="John", last_name="Doe")
|
|
214
|
+
org.users.add(user, password="123456")
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
## CLI usage
|
|
219
|
+
|
|
220
|
+
This package also includes a simple CLI program for collecting information
|
|
221
|
+
about the archive and searching and downloading documents or attachments.
|
|
222
|
+
|
|
223
|
+
First you need to log in:
|
|
224
|
+
|
|
225
|
+
```console
|
|
226
|
+
$ dw-client login --url http://localhost/ --username "Doe, John" --password FooBar --organization "Doe Inc."
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
The credentials and the session cookie are stored in the `.credentials` and
|
|
230
|
+
`.session` files in the current directory.
|
|
231
|
+
|
|
232
|
+
Of course, `--help` will give you a list of all options:
|
|
233
|
+
|
|
234
|
+
```console
|
|
235
|
+
$ dw-client --help
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Some search examples (Bash shell syntax):
|
|
239
|
+
|
|
240
|
+
```console
|
|
241
|
+
$ dw-client search --file-cabinet Archive Customer=Foo\*
|
|
242
|
+
$ dw-client search --file-cabinet Archive DocNo=123456 "DocType=Invoice \\(incoming\\)"
|
|
243
|
+
$ dw-client search --file-cabinet Archive DocDate=2022-02-14
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Downloading documents:
|
|
247
|
+
|
|
248
|
+
```console
|
|
249
|
+
$ dw-client search --file-cabinet Archive Customer=Foo\* --download document --annotations
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Downloading attachments (or sections):
|
|
253
|
+
|
|
254
|
+
```console
|
|
255
|
+
$ dw-client search --file-cabinet Archive DocNo=123456 --download attachments
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Some information about your DocuWare installation:
|
|
259
|
+
|
|
260
|
+
```console
|
|
261
|
+
$ dw-client info
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Listing all organizations, file cabinets and dialogs at once:
|
|
265
|
+
|
|
266
|
+
```console
|
|
267
|
+
$ dw-client list
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
A more specific list, only one file cabinet:
|
|
271
|
+
|
|
272
|
+
```console
|
|
273
|
+
$ dw-client list --file-cabinet Archive
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
You can also display a (partial) selection of the contents of individual fields:
|
|
277
|
+
|
|
278
|
+
```console
|
|
279
|
+
$ dw-client list --file-cabinet Archive --dialog custom --field DocNo
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
## Further reading
|
|
284
|
+
|
|
285
|
+
* Entry point to [DocuWare's official documentation][2] of the REST API.
|
|
286
|
+
* Notable endpoint: `/DocuWare/Platform/Content/PlatformLinkModel.pdf`
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
## License
|
|
290
|
+
|
|
291
|
+
This work is released under the BSD 3 license. You may use and redistribute
|
|
292
|
+
this software as long as the copyright notice is preserved.
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
[1]: https://docuware.com/
|
|
296
|
+
[2]: https://developer.docuware.com/rest/index.html
|
|
297
|
+
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
# docuware-client
|
|
2
|
+
|
|
3
|
+
This is a client library for the REST API of [DocuWare][1] DMS. Since
|
|
4
|
+
[DocuWare's documentation][2] regarding the REST API is very sparse (at the
|
|
5
|
+
time these lines were written), this client serves only a part of the API's
|
|
6
|
+
functionality.
|
|
7
|
+
|
|
8
|
+
Please keep in mind: **This software is not related to DocuWare.** It is a work
|
|
9
|
+
in progress, may yield unexpected results, and almost certainly contains bugs.
|
|
10
|
+
|
|
11
|
+
> ⚠️ Starting with version 0.5.0, OAuth2 authentication is the new default.
|
|
12
|
+
> Unless you explicitly request cookie authentication with
|
|
13
|
+
> `dw.login(..., cookie_auth=True)`, OAuth2 will be used. OAuth2 authentication
|
|
14
|
+
> has been available since DocuWare 7.10, and
|
|
15
|
+
> [cookie authentication will be discontinued](https://start.docuware.com/blog/product-news/docuware-sdk-discontinuation-of-cookie-authentication)
|
|
16
|
+
> with DocuWare 7.11.
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
First you have to log in and create a persistent session:
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
import json
|
|
25
|
+
import pathlib
|
|
26
|
+
import docuware
|
|
27
|
+
|
|
28
|
+
dw = docuware.Client("http://localhost")
|
|
29
|
+
session = dw.login("username", "password", "organization")
|
|
30
|
+
with open(".session", "w") as f:
|
|
31
|
+
json.dump(session, f)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
From then on you have to reuse the session, otherwise you will be locked out of
|
|
35
|
+
the DocuWare service for a period of time (10 minutes or even longer). As the
|
|
36
|
+
session cookie may change on subsequent logins, update the session file on
|
|
37
|
+
every login.
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
session_file = pathlib.Path(".session")
|
|
41
|
+
if session_file.exists():
|
|
42
|
+
with open(session_file) as f:
|
|
43
|
+
session = json.load(f)
|
|
44
|
+
else:
|
|
45
|
+
session = None
|
|
46
|
+
dw = docuware.Client("http://localhost")
|
|
47
|
+
session = dw.login("username", "password", "organization", saved_session=session)
|
|
48
|
+
with open(session_file, "w") as f:
|
|
49
|
+
json.dump(session, f)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Iterate over the organizations and file cabinets:
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
for org in dw.organizations:
|
|
56
|
+
print(org)
|
|
57
|
+
for fc in org.file_cabinets:
|
|
58
|
+
print(" ", fc)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
If you already know the ID or name of the objects, you can also access them
|
|
62
|
+
directly.
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
org = dw.organization("1")
|
|
66
|
+
fc = org.file_cabinet("Archive")
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Now some examples of how to search for documents. First you need a search
|
|
70
|
+
dialog:
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
# Let's use the first one:
|
|
74
|
+
dlg = fc.search_dialog()
|
|
75
|
+
# Or a specific search dialog:
|
|
76
|
+
dlg = fc.search_dialog("Default search dialog")
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Each search term consists of a field name and a search pattern. Each search
|
|
80
|
+
dialog knows its fields:
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
for field in dlg.fields.values():
|
|
84
|
+
print("Id =", field.id)
|
|
85
|
+
print("Length=", field.length)
|
|
86
|
+
print("Name =", field.name)
|
|
87
|
+
print("Type =", field.type)
|
|
88
|
+
print("-------")
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Let's search for some documents:
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
# Search for DOCNO equal to '123456':
|
|
95
|
+
for result in dlg.search("DOCNO=123456"):
|
|
96
|
+
print(result)
|
|
97
|
+
# Search for two patterns alternatively:
|
|
98
|
+
for result in dlg.search(["DOCNO=123456", "DOCNO=654321"], operation=docuware.OR):
|
|
99
|
+
print(result)
|
|
100
|
+
# Search for documents in a date range (01-31 January 2023):
|
|
101
|
+
for result in dlg.search("DWSTOREDATETIME=2023-01-01T00:00:00,2023-02-01T00:00:00")
|
|
102
|
+
print(result)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Please note that search terms may also contain metacharacters such as `*`, `(`,
|
|
106
|
+
`)`, which may need to be escaped when searching for these characters
|
|
107
|
+
themselves.
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
for result in dlg.search("DOCTYPE=Invoice \\(incoming\\)"):
|
|
111
|
+
print(result)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Search terms can be as simple as a single string, but can also be more complex.
|
|
115
|
+
The following two queries are equivalent:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
dlg.search(["FIELD1=TERM1,TERM2", "FIELD2=TERM3"])
|
|
119
|
+
dlg.search({"FIELD1": ["TERM1", "TERM2"], "FIELD2": ["TERM3"]})
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
The result of a search is always an iterator over the search results, even if
|
|
123
|
+
no result was obtained. Each individual search result holds a `document`
|
|
124
|
+
attribute, which gives access to the document in the archive. The document
|
|
125
|
+
itself can be downloaded as a whole or only individual attachments.
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
for result in dlg.search("DOCNO=123456"):
|
|
129
|
+
doc = result.document
|
|
130
|
+
# Download the complete document ...
|
|
131
|
+
data, content_type, filename = doc.download(keep_annotations=True)
|
|
132
|
+
docuware.write_binary_file(data, filename)
|
|
133
|
+
# ... or individual attachments (or sections, as DocuWare calls them)
|
|
134
|
+
for att in doc.attachments:
|
|
135
|
+
data, content_type, filename = att.download()
|
|
136
|
+
docuware.write_binary_file(data, filename)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Create data entry in file cabinet:
|
|
140
|
+
```python
|
|
141
|
+
data = {
|
|
142
|
+
"FIELD1": "value1",
|
|
143
|
+
"FIELD2": "value2",
|
|
144
|
+
}
|
|
145
|
+
response = fc.create_data_entry(data)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
_Subject to rewrite:_ Update data fields of document. The search parameter must
|
|
149
|
+
return a single document. Use a loop to execute this function on multiple
|
|
150
|
+
documents:
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
fields = {
|
|
154
|
+
"FIELD1": "value1",
|
|
155
|
+
"FIELD2": 99999
|
|
156
|
+
}
|
|
157
|
+
response = fc.update_data_entry(["FIELD1=TERM1,TERM2", "FIELD2=TERM3"], user_fields)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Delete documents:
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
dlg = fc.search_dialog()
|
|
164
|
+
for result in dlg.search(["FIELD1=TERM1,TERM2", "FIELD2=TERM3"]):
|
|
165
|
+
document = result.document
|
|
166
|
+
document.delete()
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Users and groups of an organisation can be accessed and managed:
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
# Iterate over the list of users and groups:
|
|
173
|
+
for user in org.users:
|
|
174
|
+
print(user)
|
|
175
|
+
for group in org.groups:
|
|
176
|
+
print(group)
|
|
177
|
+
|
|
178
|
+
# Find a specific user:
|
|
179
|
+
user = org.users["John Doe"] # or: org.users.get("John Doe")
|
|
180
|
+
|
|
181
|
+
# Add a user to a group:
|
|
182
|
+
group = org.groups["Managers"] # or: org.groups.get("Managers")
|
|
183
|
+
group.add_user(user)
|
|
184
|
+
# or
|
|
185
|
+
user.add_to_group(group)
|
|
186
|
+
|
|
187
|
+
# Deactivate user:
|
|
188
|
+
user.active = False # or True to activate user
|
|
189
|
+
|
|
190
|
+
# Create a new user:
|
|
191
|
+
user = docuware.User(first_name="John", last_name="Doe")
|
|
192
|
+
org.users.add(user, password="123456")
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
## CLI usage
|
|
197
|
+
|
|
198
|
+
This package also includes a simple CLI program for collecting information
|
|
199
|
+
about the archive and searching and downloading documents or attachments.
|
|
200
|
+
|
|
201
|
+
First you need to log in:
|
|
202
|
+
|
|
203
|
+
```console
|
|
204
|
+
$ dw-client login --url http://localhost/ --username "Doe, John" --password FooBar --organization "Doe Inc."
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
The credentials and the session cookie are stored in the `.credentials` and
|
|
208
|
+
`.session` files in the current directory.
|
|
209
|
+
|
|
210
|
+
Of course, `--help` will give you a list of all options:
|
|
211
|
+
|
|
212
|
+
```console
|
|
213
|
+
$ dw-client --help
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Some search examples (Bash shell syntax):
|
|
217
|
+
|
|
218
|
+
```console
|
|
219
|
+
$ dw-client search --file-cabinet Archive Customer=Foo\*
|
|
220
|
+
$ dw-client search --file-cabinet Archive DocNo=123456 "DocType=Invoice \\(incoming\\)"
|
|
221
|
+
$ dw-client search --file-cabinet Archive DocDate=2022-02-14
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Downloading documents:
|
|
225
|
+
|
|
226
|
+
```console
|
|
227
|
+
$ dw-client search --file-cabinet Archive Customer=Foo\* --download document --annotations
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Downloading attachments (or sections):
|
|
231
|
+
|
|
232
|
+
```console
|
|
233
|
+
$ dw-client search --file-cabinet Archive DocNo=123456 --download attachments
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Some information about your DocuWare installation:
|
|
237
|
+
|
|
238
|
+
```console
|
|
239
|
+
$ dw-client info
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Listing all organizations, file cabinets and dialogs at once:
|
|
243
|
+
|
|
244
|
+
```console
|
|
245
|
+
$ dw-client list
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
A more specific list, only one file cabinet:
|
|
249
|
+
|
|
250
|
+
```console
|
|
251
|
+
$ dw-client list --file-cabinet Archive
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
You can also display a (partial) selection of the contents of individual fields:
|
|
255
|
+
|
|
256
|
+
```console
|
|
257
|
+
$ dw-client list --file-cabinet Archive --dialog custom --field DocNo
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
## Further reading
|
|
262
|
+
|
|
263
|
+
* Entry point to [DocuWare's official documentation][2] of the REST API.
|
|
264
|
+
* Notable endpoint: `/DocuWare/Platform/Content/PlatformLinkModel.pdf`
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
## License
|
|
268
|
+
|
|
269
|
+
This work is released under the BSD 3 license. You may use and redistribute
|
|
270
|
+
this software as long as the copyright notice is preserved.
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
[1]: https://docuware.com/
|
|
274
|
+
[2]: https://developer.docuware.com/rest/index.html
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from docuware.client import DocuwareClient
|
|
2
|
+
from docuware.dialogs import SearchDialog
|
|
3
|
+
from docuware.errors import *
|
|
4
|
+
from docuware.filecabinet import FileCabinet
|
|
5
|
+
from docuware.organization import Organization
|
|
6
|
+
from docuware.users import User, Group
|
|
7
|
+
from docuware.utils import unique_filename, write_binary_file, random_password
|
|
8
|
+
|
|
9
|
+
Client = DocuwareClient
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Case-insensitive dictionary.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
from collections.abc import MutableMapping, ItemsView, KeysView, ValuesView
|
|
7
|
+
from typing import Any, Dict, Generator, Generic, Iterator, Optional, Tuple, TypeVar
|
|
8
|
+
|
|
9
|
+
VT = TypeVar('VT')
|
|
10
|
+
|
|
11
|
+
class CaseInsensitiveDict(MutableMapping[str, VT], Generic[VT]):
|
|
12
|
+
def __init__(self, initial_values: Any = None, **kwargs: VT) -> None:
|
|
13
|
+
self._items: Dict[str, Tuple[str, VT]] = dict()
|
|
14
|
+
if initial_values is not None:
|
|
15
|
+
if isinstance(initial_values, (dict, CaseInsensitiveDict)):
|
|
16
|
+
for key, value in initial_values.items():
|
|
17
|
+
self.__setitem__(key, value)
|
|
18
|
+
elif isinstance(initial_values, (list, tuple)):
|
|
19
|
+
for (key, value) in initial_values:
|
|
20
|
+
self.__setitem__(key, value)
|
|
21
|
+
else:
|
|
22
|
+
raise TypeError("Unsupported type for initial_values")
|
|
23
|
+
for key, value in kwargs.items():
|
|
24
|
+
self.__setitem__(key, value)
|
|
25
|
+
|
|
26
|
+
@staticmethod
|
|
27
|
+
def _strip_case(key: Any) -> str:
|
|
28
|
+
return str(key).casefold()
|
|
29
|
+
|
|
30
|
+
def __contains__(self, key: Any) -> bool:
|
|
31
|
+
return self._items.__contains__(self._strip_case(key))
|
|
32
|
+
|
|
33
|
+
def __getitem__(self, key: str) -> VT:
|
|
34
|
+
try:
|
|
35
|
+
return self._items[self._strip_case(key)][1]
|
|
36
|
+
except KeyError:
|
|
37
|
+
raise KeyError(key)
|
|
38
|
+
|
|
39
|
+
def __setitem__(self, key: str, value: VT) -> None:
|
|
40
|
+
self._items[self._strip_case(key)] = (key, value)
|
|
41
|
+
|
|
42
|
+
def __iter__(self) -> Generator[str, None, None]:
|
|
43
|
+
return (item[0] for item in self._items.values())
|
|
44
|
+
|
|
45
|
+
def __delitem__(self, key: str) -> None:
|
|
46
|
+
del self._items[self._strip_case(key)]
|
|
47
|
+
|
|
48
|
+
def __len__(self) -> int:
|
|
49
|
+
return len(self._items)
|
|
50
|
+
|
|
51
|
+
def __eq__(self, other: Any) -> bool:
|
|
52
|
+
if not isinstance(other, CaseInsensitiveDict):
|
|
53
|
+
return NotImplemented
|
|
54
|
+
return dict(self.case_insensitive_items()) == dict(other.case_insensitive_items())
|
|
55
|
+
|
|
56
|
+
def get(self, key: str, default: Optional[VT] = None) -> Optional[VT]:
|
|
57
|
+
try:
|
|
58
|
+
return self.__getitem__(key)
|
|
59
|
+
except KeyError:
|
|
60
|
+
return default
|
|
61
|
+
|
|
62
|
+
def items(self) -> ItemsView[str, VT]:
|
|
63
|
+
return ItemsView(self)
|
|
64
|
+
|
|
65
|
+
def keys(self) -> KeysView[str]:
|
|
66
|
+
return KeysView(self)
|
|
67
|
+
|
|
68
|
+
def values(self) -> ValuesView[VT]:
|
|
69
|
+
return ValuesView(self)
|
|
70
|
+
|
|
71
|
+
def case_insensitive_items(self) -> Generator[Tuple[str, VT], None, None]:
|
|
72
|
+
return ((k, v[1]) for (k, v) in self._items.items())
|
|
73
|
+
|
|
74
|
+
def copy(self) -> CaseInsensitiveDict[VT]:
|
|
75
|
+
return CaseInsensitiveDict(list(self._items.values()))
|
|
76
|
+
|
|
77
|
+
def __repr__(self) -> str:
|
|
78
|
+
return str(dict(self.items()))
|
|
79
|
+
|
|
80
|
+
# vim: set et sw=4 ts=4:
|