girder-sample-tracker 2.0.1__tar.gz → 2.0.2__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.
- {girder_sample_tracker-2.0.1/girder_sample_tracker.egg-info → girder_sample_tracker-2.0.2}/PKG-INFO +3 -2
- {girder_sample_tracker-2.0.1 → girder_sample_tracker-2.0.2}/girder_sample_tracker/models/sample.py +9 -0
- {girder_sample_tracker-2.0.1 → girder_sample_tracker-2.0.2}/girder_sample_tracker/rest/sample.py +50 -17
- girder_sample_tracker-2.0.2/girder_sample_tracker/web_client/dist/girder-plugin-sample-tracker.umd.cjs +77 -0
- {girder_sample_tracker-2.0.1 → girder_sample_tracker-2.0.2/girder_sample_tracker.egg-info}/PKG-INFO +3 -2
- {girder_sample_tracker-2.0.1 → girder_sample_tracker-2.0.2}/setup.py +1 -1
- girder_sample_tracker-2.0.1/girder_sample_tracker/web_client/dist/girder-plugin-sample-tracker.umd.cjs +0 -72
- {girder_sample_tracker-2.0.1 → girder_sample_tracker-2.0.2}/.github/workflows/release.yml +0 -0
- {girder_sample_tracker-2.0.1 → girder_sample_tracker-2.0.2}/.gitignore +0 -0
- {girder_sample_tracker-2.0.1 → girder_sample_tracker-2.0.2}/LICENSE +0 -0
- {girder_sample_tracker-2.0.1 → girder_sample_tracker-2.0.2}/README.md +0 -0
- {girder_sample_tracker-2.0.1 → girder_sample_tracker-2.0.2}/girder_sample_tracker/__init__.py +0 -0
- {girder_sample_tracker-2.0.1 → girder_sample_tracker-2.0.2}/girder_sample_tracker/models/__init__.py +0 -0
- {girder_sample_tracker-2.0.1 → girder_sample_tracker-2.0.2}/girder_sample_tracker/rest/__init__.py +0 -0
- {girder_sample_tracker-2.0.1 → girder_sample_tracker-2.0.2}/girder_sample_tracker/web_client/dist/style.css +0 -0
- {girder_sample_tracker-2.0.1 → girder_sample_tracker-2.0.2}/girder_sample_tracker.egg-info/SOURCES.txt +0 -0
- {girder_sample_tracker-2.0.1 → girder_sample_tracker-2.0.2}/girder_sample_tracker.egg-info/dependency_links.txt +0 -0
- {girder_sample_tracker-2.0.1 → girder_sample_tracker-2.0.2}/girder_sample_tracker.egg-info/entry_points.txt +0 -0
- {girder_sample_tracker-2.0.1 → girder_sample_tracker-2.0.2}/girder_sample_tracker.egg-info/not-zip-safe +0 -0
- {girder_sample_tracker-2.0.1 → girder_sample_tracker-2.0.2}/girder_sample_tracker.egg-info/requires.txt +0 -0
- {girder_sample_tracker-2.0.1 → girder_sample_tracker-2.0.2}/girder_sample_tracker.egg-info/top_level.txt +0 -0
- {girder_sample_tracker-2.0.1 → girder_sample_tracker-2.0.2}/setup.cfg +0 -0
{girder_sample_tracker-2.0.1/girder_sample_tracker.egg-info → girder_sample_tracker-2.0.2}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: girder-sample-tracker
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.2
|
|
4
4
|
Summary: Girder Plugin adding a simple tracker for material samples
|
|
5
5
|
License: BSD
|
|
6
6
|
Classifier: Development Status :: 4 - Beta
|
|
@@ -19,6 +19,7 @@ Dynamic: classifier
|
|
|
19
19
|
Dynamic: description
|
|
20
20
|
Dynamic: description-content-type
|
|
21
21
|
Dynamic: license
|
|
22
|
+
Dynamic: license-file
|
|
22
23
|
Dynamic: requires-dist
|
|
23
24
|
Dynamic: requires-python
|
|
24
25
|
Dynamic: summary
|
{girder_sample_tracker-2.0.1 → girder_sample_tracker-2.0.2}/girder_sample_tracker/models/sample.py
RENAMED
|
@@ -64,6 +64,15 @@ class Sample(AccessControlledModel):
|
|
|
64
64
|
|
|
65
65
|
return sample
|
|
66
66
|
|
|
67
|
+
def remove_event(self, sample, event, user=None):
|
|
68
|
+
self.collection.update_one(
|
|
69
|
+
{
|
|
70
|
+
"_id": sample["_id"],
|
|
71
|
+
},
|
|
72
|
+
{"$pull": {"events": {**event}}},
|
|
73
|
+
)
|
|
74
|
+
return self.load(sample["_id"], user=user)
|
|
75
|
+
|
|
67
76
|
def qr_code(self, sample, url):
|
|
68
77
|
buf = io.BytesIO()
|
|
69
78
|
qr = qrcode.QRCode(
|
{girder_sample_tracker-2.0.1 → girder_sample_tracker-2.0.2}/girder_sample_tracker/rest/sample.py
RENAMED
|
@@ -3,6 +3,7 @@ import math
|
|
|
3
3
|
from urllib.parse import urlparse
|
|
4
4
|
|
|
5
5
|
import cherrypy
|
|
6
|
+
import dateutil.parser
|
|
6
7
|
from girder.api import access
|
|
7
8
|
from girder.api.describe import Description, autoDescribeRoute
|
|
8
9
|
from girder.api.rest import (
|
|
@@ -13,23 +14,13 @@ from girder.api.rest import (
|
|
|
13
14
|
)
|
|
14
15
|
from girder.constants import AccessType, SortDir, TokenScope
|
|
15
16
|
from girder.exceptions import RestException, ValidationException
|
|
17
|
+
from girder.models.user import User
|
|
16
18
|
from girder.utility import ziputil
|
|
17
19
|
from girder.utility.progress import ProgressContext
|
|
18
20
|
|
|
19
21
|
from ..models.sample import Sample as SampleModel
|
|
20
22
|
|
|
21
23
|
|
|
22
|
-
def bytesio_iterator(bio, chunk_size=4096):
|
|
23
|
-
"""
|
|
24
|
-
Iterates over the content of a BytesIO object in chunks.
|
|
25
|
-
"""
|
|
26
|
-
while True:
|
|
27
|
-
chunk = bio.read(chunk_size)
|
|
28
|
-
if not chunk:
|
|
29
|
-
break
|
|
30
|
-
yield chunk
|
|
31
|
-
|
|
32
|
-
|
|
33
24
|
class Sample(Resource):
|
|
34
25
|
def __init__(self):
|
|
35
26
|
super(Sample, self).__init__()
|
|
@@ -46,17 +37,25 @@ class Sample(Resource):
|
|
|
46
37
|
self.route("GET", (":id", "access"), self.get_access)
|
|
47
38
|
self.route("PUT", (":id", "access"), self.update_access)
|
|
48
39
|
self.route("POST", (":id", "event"), self.create_event)
|
|
40
|
+
self.route("DELETE", (":id", "event"), self.delete_event)
|
|
49
41
|
|
|
50
42
|
@access.public
|
|
51
43
|
@autoDescribeRoute(
|
|
52
|
-
Description("List samples")
|
|
44
|
+
Description("List samples")
|
|
45
|
+
.param("query", "A regular expression to filter sample names", required=False)
|
|
46
|
+
.pagingParams(
|
|
53
47
|
defaultSort="name", defaultSortDir=SortDir.DESCENDING
|
|
54
48
|
)
|
|
55
49
|
)
|
|
56
50
|
@filtermodel(model="sample", plugin="sample_tracker")
|
|
57
|
-
def list_samples(self, limit, offset, sort):
|
|
51
|
+
def list_samples(self, query, limit, offset, sort):
|
|
52
|
+
if query:
|
|
53
|
+
query = {"name": {"$regex": query, "$options": "i"}}
|
|
54
|
+
else:
|
|
55
|
+
query = {}
|
|
56
|
+
|
|
58
57
|
return SampleModel().findWithPermissions(
|
|
59
|
-
query=
|
|
58
|
+
query=query,
|
|
60
59
|
offset=offset,
|
|
61
60
|
limit=limit,
|
|
62
61
|
sort=sort,
|
|
@@ -130,15 +129,25 @@ class Sample(Resource):
|
|
|
130
129
|
raise ValidationException(
|
|
131
130
|
"Batch size must be at least 1, but no more than 64."
|
|
132
131
|
)
|
|
132
|
+
|
|
133
133
|
if not eventTypes:
|
|
134
134
|
eventTypes = []
|
|
135
135
|
user = self.getCurrentUser()
|
|
136
136
|
samples = []
|
|
137
137
|
if batchSize > 1:
|
|
138
|
-
|
|
138
|
+
if "{number" not in name:
|
|
139
|
+
name = name + "{number:0" + str(math.ceil(math.log10(batchSize))) + "d}"
|
|
140
|
+
|
|
141
|
+
try:
|
|
142
|
+
name.format(number=1)
|
|
143
|
+
except KeyError:
|
|
144
|
+
raise ValidationException(
|
|
145
|
+
"Name must contain a '{number}' placeholder for batch creation."
|
|
146
|
+
)
|
|
147
|
+
|
|
139
148
|
for i in range(batchSize):
|
|
140
149
|
sample = SampleModel().create(
|
|
141
|
-
|
|
150
|
+
name.format(number=i + 1),
|
|
142
151
|
user,
|
|
143
152
|
description=description,
|
|
144
153
|
eventTypes=eventTypes,
|
|
@@ -300,6 +309,26 @@ class Sample(Resource):
|
|
|
300
309
|
}
|
|
301
310
|
return SampleModel().add_event(sample, event)
|
|
302
311
|
|
|
312
|
+
@access.user
|
|
313
|
+
@autoDescribeRoute(
|
|
314
|
+
Description("Delete an event for a sample")
|
|
315
|
+
.modelParam(
|
|
316
|
+
"id", "The ID of the sample", model=SampleModel, level=AccessType.ADMIN
|
|
317
|
+
)
|
|
318
|
+
.jsonParam("event", "Event to remove", required=True, requireObject=True)
|
|
319
|
+
)
|
|
320
|
+
@filtermodel(model="sample", plugin="sample_tracker")
|
|
321
|
+
def delete_event(self, sample, event):
|
|
322
|
+
creator = User().load(event["creator"], force=True)
|
|
323
|
+
processed_event = {
|
|
324
|
+
"created": dateutil.parser.parse(event.get("created")),
|
|
325
|
+
"creator": creator["_id"],
|
|
326
|
+
"eventType": event.get("eventType"),
|
|
327
|
+
}
|
|
328
|
+
return SampleModel().remove_event(
|
|
329
|
+
sample, processed_event, user=self.getCurrentUser()
|
|
330
|
+
)
|
|
331
|
+
|
|
303
332
|
@access.public(scope=TokenScope.DATA_READ, cookie=True)
|
|
304
333
|
@autoDescribeRoute(
|
|
305
334
|
Description("Download a sample").modelParam(
|
|
@@ -312,7 +341,11 @@ class Sample(Resource):
|
|
|
312
341
|
qr_img = SampleModel().qr_code(sample, girder_base)
|
|
313
342
|
setResponseHeader("Content-Type", "image/png")
|
|
314
343
|
setContentDisposition(f"{sample['name']}.png")
|
|
315
|
-
|
|
344
|
+
|
|
345
|
+
def stream():
|
|
346
|
+
yield from qr_img
|
|
347
|
+
|
|
348
|
+
return stream
|
|
316
349
|
|
|
317
350
|
@access.public(scope=TokenScope.DATA_READ, cookie=True)
|
|
318
351
|
@autoDescribeRoute(
|