zou 0.19.15__py3-none-any.whl → 0.20.11__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.
- zou/__init__.py +1 -1
- zou/app/__init__.py +10 -2
- zou/app/api.py +2 -0
- zou/app/blueprints/assets/__init__.py +22 -0
- zou/app/blueprints/assets/resources.py +241 -4
- zou/app/blueprints/auth/__init__.py +4 -0
- zou/app/blueprints/auth/resources.py +154 -22
- zou/app/blueprints/breakdown/resources.py +4 -4
- zou/app/blueprints/chats/__init__.py +22 -0
- zou/app/blueprints/chats/resources.py +199 -0
- zou/app/blueprints/comments/resources.py +36 -19
- zou/app/blueprints/crud/__init__.py +12 -0
- zou/app/blueprints/crud/attachment_file.py +14 -5
- zou/app/blueprints/crud/base.py +29 -28
- zou/app/blueprints/crud/chat.py +13 -0
- zou/app/blueprints/crud/chat_message.py +13 -0
- zou/app/blueprints/crud/comments.py +85 -29
- zou/app/blueprints/crud/custom_action.py +1 -1
- zou/app/blueprints/crud/day_off.py +47 -9
- zou/app/blueprints/crud/department.py +1 -25
- zou/app/blueprints/crud/entity.py +46 -5
- zou/app/blueprints/crud/entity_type.py +13 -1
- zou/app/blueprints/crud/event.py +1 -1
- zou/app/blueprints/crud/file_status.py +1 -1
- zou/app/blueprints/crud/metadata_descriptor.py +24 -10
- zou/app/blueprints/crud/organisation.py +22 -5
- zou/app/blueprints/crud/output_file.py +1 -1
- zou/app/blueprints/crud/output_type.py +1 -1
- zou/app/blueprints/crud/person.py +32 -24
- zou/app/blueprints/crud/playlist.py +1 -1
- zou/app/blueprints/crud/preview_background_file.py +6 -7
- zou/app/blueprints/crud/preview_file.py +1 -1
- zou/app/blueprints/crud/project.py +14 -6
- zou/app/blueprints/crud/project_status.py +1 -1
- zou/app/blueprints/crud/schedule_item.py +4 -2
- zou/app/blueprints/crud/software.py +1 -1
- zou/app/blueprints/crud/status_automation.py +1 -1
- zou/app/blueprints/crud/studio.py +33 -0
- zou/app/blueprints/crud/task.py +47 -3
- zou/app/blueprints/crud/task_status.py +1 -1
- zou/app/blueprints/crud/task_type.py +4 -4
- zou/app/blueprints/crud/working_file.py +4 -8
- zou/app/blueprints/events/resources.py +13 -12
- zou/app/blueprints/export/csv/assets.py +15 -6
- zou/app/blueprints/export/csv/edits.py +15 -5
- zou/app/blueprints/export/csv/playlists.py +1 -1
- zou/app/blueprints/export/csv/shots.py +15 -5
- zou/app/blueprints/export/csv/time_spents.py +1 -1
- zou/app/blueprints/files/resources.py +22 -23
- zou/app/blueprints/index/resources.py +38 -29
- zou/app/blueprints/news/resources.py +25 -11
- zou/app/blueprints/persons/__init__.py +5 -2
- zou/app/blueprints/persons/resources.py +126 -120
- zou/app/blueprints/previews/__init__.py +18 -8
- zou/app/blueprints/previews/resources.py +569 -328
- zou/app/blueprints/projects/resources.py +1 -1
- zou/app/blueprints/search/resources.py +18 -6
- zou/app/blueprints/shots/__init__.py +5 -0
- zou/app/blueprints/shots/resources.py +134 -4
- zou/app/blueprints/source/__init__.py +6 -6
- zou/app/blueprints/source/csv/assets.py +10 -3
- zou/app/blueprints/source/csv/base.py +1 -1
- zou/app/blueprints/source/csv/edits.py +10 -3
- zou/app/blueprints/source/csv/shots.py +10 -3
- zou/app/blueprints/source/{edl.py → otio.py} +82 -41
- zou/app/blueprints/tasks/__init__.py +3 -2
- zou/app/blueprints/tasks/resources.py +83 -52
- zou/app/blueprints/user/__init__.py +9 -0
- zou/app/blueprints/user/resources.py +170 -12
- zou/app/config.py +10 -0
- zou/app/mixin.py +6 -5
- zou/app/models/attachment_file.py +10 -4
- zou/app/models/base.py +18 -13
- zou/app/models/build_job.py +7 -4
- zou/app/models/chat.py +44 -0
- zou/app/models/chat_message.py +37 -0
- zou/app/models/comment.py +1 -0
- zou/app/models/day_off.py +3 -0
- zou/app/models/entity.py +4 -6
- zou/app/models/entity_type.py +2 -0
- zou/app/models/organisation.py +14 -15
- zou/app/models/person.py +6 -1
- zou/app/models/project.py +3 -0
- zou/app/models/search_filter.py +11 -0
- zou/app/models/search_filter_group.py +10 -0
- zou/app/models/serializer.py +17 -17
- zou/app/models/status_automation.py +2 -0
- zou/app/models/studio.py +13 -0
- zou/app/models/subscription.py +2 -2
- zou/app/models/task.py +6 -1
- zou/app/models/task_status.py +1 -0
- zou/app/models/task_type.py +1 -0
- zou/app/models/working_file.py +1 -1
- zou/app/services/assets_service.py +101 -14
- zou/app/services/auth_service.py +17 -44
- zou/app/services/breakdown_service.py +37 -5
- zou/app/services/chats_service.py +279 -0
- zou/app/services/comments_service.py +110 -65
- zou/app/services/concepts_service.py +4 -12
- zou/app/services/deletion_service.py +43 -30
- zou/app/services/edits_service.py +5 -11
- zou/app/services/emails_service.py +4 -4
- zou/app/services/entities_service.py +17 -2
- zou/app/services/events_service.py +12 -4
- zou/app/services/exception.py +5 -5
- zou/app/services/names_service.py +7 -2
- zou/app/services/news_service.py +17 -9
- zou/app/services/persons_service.py +38 -21
- zou/app/services/playlists_service.py +8 -7
- zou/app/services/preview_files_service.py +137 -10
- zou/app/services/projects_service.py +5 -14
- zou/app/services/shots_service.py +221 -49
- zou/app/services/sync_service.py +46 -42
- zou/app/services/tasks_service.py +185 -46
- zou/app/services/time_spents_service.py +67 -20
- zou/app/services/user_service.py +350 -107
- zou/app/stores/auth_tokens_store.py +2 -1
- zou/app/stores/file_store.py +18 -0
- zou/app/stores/publisher_store.py +7 -7
- zou/app/stores/queue_store.py +1 -0
- zou/app/swagger.py +36 -20
- zou/app/utils/cache.py +2 -0
- zou/app/utils/commands.py +104 -7
- zou/app/utils/csv_utils.py +1 -4
- zou/app/utils/date_helpers.py +33 -17
- zou/app/utils/dbhelpers.py +14 -1
- zou/app/utils/emails.py +2 -2
- zou/app/utils/fido.py +22 -0
- zou/app/utils/query.py +54 -6
- zou/app/utils/redis.py +11 -0
- zou/app/utils/saml.py +51 -0
- zou/app/utils/string.py +2 -0
- zou/app/utils/thumbnail.py +4 -2
- zou/cli.py +76 -18
- zou/debug.py +4 -2
- zou/event_stream.py +122 -165
- zou/job_settings.py +1 -0
- zou/migrations/env.py +0 -0
- zou/migrations/utils/base.py +6 -6
- zou/migrations/versions/1bb55759146f_add_table_studio.py +67 -0
- zou/migrations/versions/1fab8c420678_add_attachments_to_message_chats.py +56 -0
- zou/migrations/versions/23122f290ca2_add_entity_chat_models.py +149 -0
- zou/migrations/versions/32f134ff1201_add_is_shared_flag_to_filters.py +33 -0
- zou/migrations/versions/57222395f2be_add_statusautomation_import_last_revision.py +41 -0
- zou/migrations/versions/59a7445a966c_add_entity_is_shared.py +41 -0
- zou/migrations/versions/5b980f0dc365_add_comment_links.py +35 -0
- zou/migrations/versions/680c64565f9d_for_searchfiltergroup_is_shared.py +35 -0
- zou/migrations/versions/8e67c183bed7_add_preference_fields.py +71 -0
- zou/migrations/versions/92b40d79ad3f_allow_message_attachments.py +38 -0
- zou/migrations/versions/971dbf5a0faf_add_short_name_for_asset_type_entity_.py +33 -0
- zou/migrations/versions/9b85c14fa8a7_add_day_off_new_columns.py +68 -0
- zou/migrations/versions/9d3bb33c6fc6_add_department_keys_to_filter_models.py +73 -0
- zou/migrations/versions/a252a094e977_add_descriptions_for_entities_tasks_and_.py +40 -0
- zou/migrations/versions/be56dc0fb760_for_is_shared_disallow_nullable.py +102 -0
- zou/migrations/versions/ca28796a2a62_add_is_done_field_to_the_task_model.py +108 -0
- zou/migrations/versions/f344b867a911_for_description_of_entity_task_working_.py +75 -0
- zou/remote/config_payload.py +2 -1
- zou/utils/movie.py +14 -4
- {zou-0.19.15.dist-info → zou-0.20.11.dist-info}/METADATA +75 -69
- {zou-0.19.15.dist-info → zou-0.20.11.dist-info}/RECORD +163 -134
- {zou-0.19.15.dist-info → zou-0.20.11.dist-info}/WHEEL +1 -1
- {zou-0.19.15.dist-info → zou-0.20.11.dist-info}/LICENSE +0 -0
- {zou-0.19.15.dist-info → zou-0.20.11.dist-info}/entry_points.txt +0 -0
- {zou-0.19.15.dist-info → zou-0.20.11.dist-info}/top_level.txt +0 -0
|
@@ -9,6 +9,7 @@ try:
|
|
|
9
9
|
host=config.KEY_VALUE_STORE["host"],
|
|
10
10
|
port=config.KEY_VALUE_STORE["port"],
|
|
11
11
|
db=config.AUTH_TOKEN_BLACKLIST_KV_INDEX,
|
|
12
|
+
password=config.KEY_VALUE_STORE["password"],
|
|
12
13
|
decode_responses=True,
|
|
13
14
|
)
|
|
14
15
|
revoked_tokens_store.ping()
|
|
@@ -58,4 +59,4 @@ def is_revoked(jti):
|
|
|
58
59
|
"""
|
|
59
60
|
Tell if a stored auth token is revoked or not.
|
|
60
61
|
"""
|
|
61
|
-
return get(jti)
|
|
62
|
+
return get(jti) == "true"
|
zou/app/stores/file_store.py
CHANGED
|
@@ -104,6 +104,12 @@ def get_local_picture_path(prefix, id):
|
|
|
104
104
|
return path(pictures, make_key(prefix, id))
|
|
105
105
|
|
|
106
106
|
|
|
107
|
+
def copy_picture(prefix, id, new_prefix, new_id):
|
|
108
|
+
key = make_key(prefix, id)
|
|
109
|
+
target = make_key(new_prefix, new_id)
|
|
110
|
+
return pictures.copy(key, target)
|
|
111
|
+
|
|
112
|
+
|
|
107
113
|
def add_movie(prefix, id, path):
|
|
108
114
|
key = make_key(prefix, id)
|
|
109
115
|
with open(path, "rb") as fd:
|
|
@@ -139,6 +145,12 @@ def get_local_movie_path(prefix, id):
|
|
|
139
145
|
return path(movies, make_key(prefix, id))
|
|
140
146
|
|
|
141
147
|
|
|
148
|
+
def copy_movie(prefix, id, new_prefix, new_id):
|
|
149
|
+
key = make_key(prefix, id)
|
|
150
|
+
target = make_key(new_prefix, new_id)
|
|
151
|
+
return movies.copy(key, target)
|
|
152
|
+
|
|
153
|
+
|
|
142
154
|
def add_file(prefix, id, path):
|
|
143
155
|
key = make_key(prefix, id)
|
|
144
156
|
with open(path, "rb") as fd:
|
|
@@ -172,3 +184,9 @@ def remove_file(prefix, id):
|
|
|
172
184
|
|
|
173
185
|
def get_local_file_path(prefix, id):
|
|
174
186
|
return path(files, make_key(prefix, id))
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def copy_file(prefix, id, new_prefix, new_id):
|
|
190
|
+
key = make_key(prefix, id)
|
|
191
|
+
target = make_key(new_prefix, new_id)
|
|
192
|
+
return files.copy(key, target)
|
|
@@ -3,11 +3,7 @@ import redis
|
|
|
3
3
|
from flask_socketio import SocketIO
|
|
4
4
|
|
|
5
5
|
from zou.app import config
|
|
6
|
-
|
|
7
|
-
host = config.KEY_VALUE_STORE["host"]
|
|
8
|
-
port = config.KEY_VALUE_STORE["port"]
|
|
9
|
-
redis_db = config.KV_EVENTS_DB_INDEX
|
|
10
|
-
redis_url = "redis://%s:%s/%s" % (host, port, redis_db)
|
|
6
|
+
from zou.app.utils.redis import get_redis_url
|
|
11
7
|
|
|
12
8
|
socketio = None
|
|
13
9
|
|
|
@@ -27,11 +23,15 @@ def init():
|
|
|
27
23
|
|
|
28
24
|
try:
|
|
29
25
|
publisher_store = redis.StrictRedis(
|
|
30
|
-
host=host,
|
|
26
|
+
host=config.KEY_VALUE_STORE["host"],
|
|
27
|
+
port=config.KEY_VALUE_STORE["port"],
|
|
28
|
+
db=config.KV_EVENTS_DB_INDEX,
|
|
29
|
+
password=config.KEY_VALUE_STORE["password"],
|
|
30
|
+
decode_responses=True,
|
|
31
31
|
)
|
|
32
32
|
publisher_store.get("test")
|
|
33
33
|
socketio = SocketIO(
|
|
34
|
-
message_queue=
|
|
34
|
+
message_queue=get_redis_url(config.KV_EVENTS_DB_INDEX),
|
|
35
35
|
cors_allowed_origins=[],
|
|
36
36
|
cors_credentials=False,
|
|
37
37
|
)
|
zou/app/stores/queue_store.py
CHANGED
zou/app/swagger.py
CHANGED
|
@@ -14,6 +14,7 @@ swagger_config = {
|
|
|
14
14
|
"static_url_path": "/docs",
|
|
15
15
|
"swagger_ui": True,
|
|
16
16
|
"specs_route": "/apidocs/",
|
|
17
|
+
"openapi": "3.0.2",
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
|
|
@@ -30,26 +31,18 @@ An easy to use Python client to access this API is available:
|
|
|
30
31
|
|
|
31
32
|
<p>Before you can use any of the endpoints outline below,
|
|
32
33
|
you will have to get a JWT token to authorize your requests.
|
|
34
|
+
</p>
|
|
33
35
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
<p>
|
|
37
|
+
You will find the information to retrieve it in the
|
|
38
|
+
<a href="#tag/Authentication">Zou documentation</a>.
|
|
39
|
+
</p>
|
|
36
40
|
|
|
37
|
-
The response is a JSON object, specifically you'll need to provide the ```access_token``` for your future requests.
|
|
38
|
-
|
|
39
|
-
Here is a complete authentication process as an example (again using curl):
|
|
40
|
-
```
|
|
41
|
-
$ curl -X POST <server_address>/api/auth/login -d "email=<youremail>&password=<yourpassword>"'
|
|
42
|
-
{{"login": true", "access_token": "eyJ0e...", ...}}
|
|
43
|
-
$ jwt=eyJ0e... # Store the access token for easier use
|
|
44
|
-
$ curl -H "Authorization: Bearer $jwt" <server_address>/api/data/projects
|
|
45
|
-
[{{...}},
|
|
46
|
-
{{...}}]
|
|
47
|
-
```
|
|
48
41
|
[OpenAPI definition](/openapi.json)
|
|
49
42
|
"""
|
|
50
43
|
|
|
51
44
|
swagger_template = {
|
|
52
|
-
"openapi": "3.
|
|
45
|
+
"openapi": "3.0.2",
|
|
53
46
|
"info": {
|
|
54
47
|
"title": "Kitsu API",
|
|
55
48
|
"description": description,
|
|
@@ -70,12 +63,14 @@ swagger_template = {
|
|
|
70
63
|
"host": "localhost:8080",
|
|
71
64
|
"basePath": "/api",
|
|
72
65
|
"schemes": ["http", "https"],
|
|
73
|
-
"
|
|
74
|
-
"
|
|
75
|
-
"
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
66
|
+
"components": {
|
|
67
|
+
"securitySchemes": {
|
|
68
|
+
"JWT Authorization": {
|
|
69
|
+
"name": "Authorization",
|
|
70
|
+
"in": "header",
|
|
71
|
+
"type": "apiKey",
|
|
72
|
+
"description": "Format in header: **Authorization: Bearer {token}**. \n\n Value example: Bearer xxxxx.yyyyy.zzzzz",
|
|
73
|
+
}
|
|
79
74
|
}
|
|
80
75
|
},
|
|
81
76
|
"security": [{"JWT Authorization": []}],
|
|
@@ -345,6 +340,10 @@ swagger_template = {
|
|
|
345
340
|
"type": "string",
|
|
346
341
|
"description": "Color of department",
|
|
347
342
|
},
|
|
343
|
+
"archived": {
|
|
344
|
+
"type": "boolean",
|
|
345
|
+
"description": "True if the department is archived else False",
|
|
346
|
+
},
|
|
348
347
|
},
|
|
349
348
|
},
|
|
350
349
|
"DesktopLoginLog": {
|
|
@@ -1189,6 +1188,23 @@ swagger_template = {
|
|
|
1189
1188
|
},
|
|
1190
1189
|
},
|
|
1191
1190
|
},
|
|
1191
|
+
"Studio": {
|
|
1192
|
+
"type": "object",
|
|
1193
|
+
"properties": {
|
|
1194
|
+
"name": {
|
|
1195
|
+
"type": "string",
|
|
1196
|
+
"description": "Name of studio",
|
|
1197
|
+
},
|
|
1198
|
+
"color": {
|
|
1199
|
+
"type": "string",
|
|
1200
|
+
"description": "Color of studio",
|
|
1201
|
+
},
|
|
1202
|
+
"archived": {
|
|
1203
|
+
"type": "boolean",
|
|
1204
|
+
"description": "True if the studio is archived else False",
|
|
1205
|
+
},
|
|
1206
|
+
},
|
|
1207
|
+
},
|
|
1192
1208
|
"Task": {
|
|
1193
1209
|
"type": "object",
|
|
1194
1210
|
"properties": {
|
zou/app/utils/cache.py
CHANGED
|
@@ -17,6 +17,7 @@ try:
|
|
|
17
17
|
host=config.KEY_VALUE_STORE["host"],
|
|
18
18
|
port=config.KEY_VALUE_STORE["port"],
|
|
19
19
|
db=config.MEMOIZE_DB_INDEX,
|
|
20
|
+
password=config.KEY_VALUE_STORE["password"],
|
|
20
21
|
decode_responses=True,
|
|
21
22
|
)
|
|
22
23
|
redis_cache.get("test")
|
|
@@ -26,6 +27,7 @@ try:
|
|
|
26
27
|
"CACHE_REDIS_HOST": config.KEY_VALUE_STORE["host"],
|
|
27
28
|
"CACHE_REDIS_PORT": config.KEY_VALUE_STORE["port"],
|
|
28
29
|
"CACHE_REDIS_DB": config.MEMOIZE_DB_INDEX,
|
|
30
|
+
"CACHE_REDIS_PASSWORD": config.KEY_VALUE_STORE["password"],
|
|
29
31
|
}
|
|
30
32
|
)
|
|
31
33
|
|
zou/app/utils/commands.py
CHANGED
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
import os
|
|
4
4
|
import datetime
|
|
5
5
|
import tempfile
|
|
6
|
+
import sys
|
|
7
|
+
import shutil
|
|
6
8
|
|
|
7
9
|
|
|
8
10
|
from ldap3 import Server, Connection, ALL, NTLM, SIMPLE
|
|
9
11
|
from zou.app.utils import thumbnail as thumbnail_utils, auth
|
|
10
|
-
from zou.app.stores import auth_tokens_store, file_store
|
|
12
|
+
from zou.app.stores import auth_tokens_store, file_store, queue_store
|
|
11
13
|
from zou.app.services import (
|
|
12
14
|
assets_service,
|
|
13
15
|
backup_service,
|
|
@@ -23,6 +25,8 @@ from zou.app.services import (
|
|
|
23
25
|
tasks_service,
|
|
24
26
|
)
|
|
25
27
|
from zou.app.models.person import Person
|
|
28
|
+
from zou.app.models.preview_file import PreviewFile
|
|
29
|
+
from zou.app.models.task import Task
|
|
26
30
|
from sqlalchemy.sql.expression import not_
|
|
27
31
|
|
|
28
32
|
from zou.app.services.exception import (
|
|
@@ -390,7 +394,9 @@ def sync_with_ldap_server():
|
|
|
390
394
|
)
|
|
391
395
|
.all()
|
|
392
396
|
):
|
|
393
|
-
persons_service.update_person(
|
|
397
|
+
persons_service.update_person(
|
|
398
|
+
person.id, {"active": False}, bypass_protected_accounts=True
|
|
399
|
+
)
|
|
394
400
|
print(
|
|
395
401
|
"User %s disabled (not found in LDAP)." % person.desktop_login
|
|
396
402
|
)
|
|
@@ -414,6 +420,7 @@ def sync_with_ldap_server():
|
|
|
414
420
|
"desktop_login": user["desktop_login"],
|
|
415
421
|
"ldap_uid": user["ldap_uid"],
|
|
416
422
|
},
|
|
423
|
+
bypass_protected_accounts=True,
|
|
417
424
|
)
|
|
418
425
|
print(f"User {user['desktop_login']} updated.")
|
|
419
426
|
except IsUserLimitReachedException:
|
|
@@ -466,7 +473,9 @@ def sync_with_ldap_server():
|
|
|
466
473
|
)
|
|
467
474
|
file_store.add_picture("thumbnails", person["id"], thumbnail_png_path)
|
|
468
475
|
os.remove(thumbnail_png_path)
|
|
469
|
-
persons_service.update_person(
|
|
476
|
+
persons_service.update_person(
|
|
477
|
+
person["id"], {"has_avatar": True}, bypass_protected_accounts=True
|
|
478
|
+
)
|
|
470
479
|
|
|
471
480
|
ldap_users = get_ldap_users()
|
|
472
481
|
update_person_list_with_ldap_users(ldap_users)
|
|
@@ -529,7 +538,7 @@ def run_sync_file_change_daemon(
|
|
|
529
538
|
|
|
530
539
|
|
|
531
540
|
def import_last_changes_from_another_instance(
|
|
532
|
-
source, login, password, minutes=0,
|
|
541
|
+
source, login, password, minutes=0, limit=300
|
|
533
542
|
):
|
|
534
543
|
"""
|
|
535
544
|
Retrieve and save all the data related to most recent events from another
|
|
@@ -538,12 +547,12 @@ def import_last_changes_from_another_instance(
|
|
|
538
547
|
with app.app_context():
|
|
539
548
|
sync_service.init(source, login, password)
|
|
540
549
|
print("Last events syncing started.")
|
|
541
|
-
sync_service.run_last_events_sync(minutes=minutes,
|
|
550
|
+
sync_service.run_last_events_sync(minutes=minutes, limit=300)
|
|
542
551
|
print("Last events syncing ended.")
|
|
543
552
|
|
|
544
553
|
|
|
545
554
|
def import_last_file_changes_from_another_instance(
|
|
546
|
-
source, login, password, minutes=20,
|
|
555
|
+
source, login, password, minutes=20, limit=50, force=False
|
|
547
556
|
):
|
|
548
557
|
"""
|
|
549
558
|
Retrieve and save all the data related most to recent file events
|
|
@@ -553,7 +562,7 @@ def import_last_file_changes_from_another_instance(
|
|
|
553
562
|
with app.app_context():
|
|
554
563
|
sync_service.init(source, login, password)
|
|
555
564
|
print("Last files syncing started.")
|
|
556
|
-
sync_service.run_last_events_files(minutes=minutes,
|
|
565
|
+
sync_service.run_last_events_files(minutes=minutes, limit=50)
|
|
557
566
|
print("Last files syncing ended.")
|
|
558
567
|
|
|
559
568
|
|
|
@@ -717,3 +726,91 @@ def create_bot(
|
|
|
717
726
|
is_bot=True,
|
|
718
727
|
)
|
|
719
728
|
print(bot["access_token"])
|
|
729
|
+
|
|
730
|
+
|
|
731
|
+
def renormalize_movie_preview_files(
|
|
732
|
+
preview_file_id=None, project_id=None, all_broken=None, all_processing=None
|
|
733
|
+
):
|
|
734
|
+
with app.app_context():
|
|
735
|
+
if preview_file_id is None and not all_broken and not all_processing:
|
|
736
|
+
print("You must specify at least one option.")
|
|
737
|
+
sys.exit(1)
|
|
738
|
+
else:
|
|
739
|
+
query = PreviewFile.query.filter(
|
|
740
|
+
PreviewFile.extension == "mp4"
|
|
741
|
+
).order_by(PreviewFile.created_at.asc())
|
|
742
|
+
|
|
743
|
+
if preview_file_id is not None:
|
|
744
|
+
query = query.filter(PreviewFile.id == preview_file_id)
|
|
745
|
+
|
|
746
|
+
if project_id is not None:
|
|
747
|
+
query = query.join(Task).filter(
|
|
748
|
+
PreviewFile.project_id == project_id
|
|
749
|
+
)
|
|
750
|
+
|
|
751
|
+
if all_broken and all_processing:
|
|
752
|
+
query = query.filter(
|
|
753
|
+
PreviewFile.status.in_(("broken", "processing"))
|
|
754
|
+
)
|
|
755
|
+
elif all_broken:
|
|
756
|
+
query = query.filter(PreviewFile.status == "broken")
|
|
757
|
+
elif all_processing:
|
|
758
|
+
query = query.filter(PreviewFile.status == "processing")
|
|
759
|
+
|
|
760
|
+
preview_files = query.all()
|
|
761
|
+
len_preview_files = len(preview_files)
|
|
762
|
+
if len_preview_files == 0:
|
|
763
|
+
print("No preview files found.")
|
|
764
|
+
sys.exit(1)
|
|
765
|
+
else:
|
|
766
|
+
for i, preview_file in enumerate(preview_files):
|
|
767
|
+
try:
|
|
768
|
+
preview_file_id = str(preview_file.id)
|
|
769
|
+
print(
|
|
770
|
+
f"Renormalizing preview file {preview_file_id} ({i+1}/{len_preview_files})."
|
|
771
|
+
)
|
|
772
|
+
extension = preview_file.extension
|
|
773
|
+
uploaded_movie_path = os.path.join(
|
|
774
|
+
config.TMP_DIR,
|
|
775
|
+
f"{preview_file_id}.{extension}.tmp",
|
|
776
|
+
)
|
|
777
|
+
try:
|
|
778
|
+
if config.FS_BACKEND == "local":
|
|
779
|
+
shutil.copyfile(
|
|
780
|
+
file_store.get_local_movie_path(
|
|
781
|
+
"source", preview_file_id
|
|
782
|
+
),
|
|
783
|
+
uploaded_movie_path,
|
|
784
|
+
)
|
|
785
|
+
else:
|
|
786
|
+
sync_service.download_file(
|
|
787
|
+
uploaded_movie_path,
|
|
788
|
+
"source",
|
|
789
|
+
file_store.open_movie,
|
|
790
|
+
str(preview_file_id),
|
|
791
|
+
)
|
|
792
|
+
except:
|
|
793
|
+
pass
|
|
794
|
+
if config.ENABLE_JOB_QUEUE:
|
|
795
|
+
queue_store.job_queue.enqueue(
|
|
796
|
+
preview_files_service.prepare_and_store_movie,
|
|
797
|
+
args=(
|
|
798
|
+
preview_file_id,
|
|
799
|
+
uploaded_movie_path,
|
|
800
|
+
True,
|
|
801
|
+
False,
|
|
802
|
+
),
|
|
803
|
+
job_timeout=int(config.JOB_QUEUE_TIMEOUT),
|
|
804
|
+
)
|
|
805
|
+
else:
|
|
806
|
+
preview_files_service.prepare_and_store_movie(
|
|
807
|
+
preview_file_id,
|
|
808
|
+
uploaded_movie_path,
|
|
809
|
+
normalize=True,
|
|
810
|
+
add_source_to_file_store=False,
|
|
811
|
+
)
|
|
812
|
+
except Exception as e:
|
|
813
|
+
print(
|
|
814
|
+
f"Renormalization of preview file {preview_file_id} failed: {e}"
|
|
815
|
+
)
|
|
816
|
+
continue
|
zou/app/utils/csv_utils.py
CHANGED
zou/app/utils/date_helpers.py
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import isoweek
|
|
2
|
+
import datetime
|
|
2
3
|
|
|
3
4
|
from babel.dates import format_datetime
|
|
4
|
-
from datetime import date, datetime, timedelta
|
|
5
5
|
from dateutil import relativedelta
|
|
6
6
|
from zou.app.services.exception import WrongDateFormatException
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
def get_now():
|
|
10
|
-
return get_string_with_timezone_from_date(
|
|
10
|
+
return get_string_with_timezone_from_date(get_utc_now_datetime(), "UTC")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_utc_now_datetime():
|
|
14
|
+
return datetime.datetime.now(tz=datetime.timezone.utc).replace(tzinfo=None)
|
|
11
15
|
|
|
12
16
|
|
|
13
17
|
def get_date_from_now(nb_days):
|
|
14
|
-
return date.today() - timedelta(days=nb_days)
|
|
18
|
+
return datetime.date.today() - datetime.timedelta(days=nb_days)
|
|
15
19
|
|
|
16
20
|
|
|
17
21
|
def get_date_diff(date_a, date_b):
|
|
@@ -29,7 +33,7 @@ def get_date_string_with_timezone(date_string, timezone):
|
|
|
29
33
|
"""
|
|
30
34
|
Apply given timezone to given date and return it as a string.
|
|
31
35
|
"""
|
|
32
|
-
date_obj = datetime.strptime(date_string, "%Y-%m-%dT%H:%M:%S")
|
|
36
|
+
date_obj = datetime.datetime.strptime(date_string, "%Y-%m-%dT%H:%M:%S")
|
|
33
37
|
return format_datetime(date_obj, "yyyy-MM-ddTHH:mm:ss", tzinfo=timezone)
|
|
34
38
|
|
|
35
39
|
|
|
@@ -51,14 +55,16 @@ def get_today_string_with_timezone(timezone):
|
|
|
51
55
|
"""
|
|
52
56
|
Get today date in string format with timezone applied.
|
|
53
57
|
"""
|
|
54
|
-
return get_simple_string_with_timezone_from_date(
|
|
58
|
+
return get_simple_string_with_timezone_from_date(
|
|
59
|
+
datetime.date.today(), timezone
|
|
60
|
+
)
|
|
55
61
|
|
|
56
62
|
|
|
57
63
|
def get_date_from_string(date_str):
|
|
58
64
|
"""
|
|
59
65
|
Parse a date string and returns a date object.
|
|
60
66
|
"""
|
|
61
|
-
return datetime.strptime(date_str, "%Y-%m-%d")
|
|
67
|
+
return datetime.datetime.strptime(date_str, "%Y-%m-%d")
|
|
62
68
|
|
|
63
69
|
|
|
64
70
|
def get_datetime_from_string(date_str, milliseconds=False):
|
|
@@ -66,9 +72,9 @@ def get_datetime_from_string(date_str, milliseconds=False):
|
|
|
66
72
|
Parse a datetime string and returns a datetime object.
|
|
67
73
|
"""
|
|
68
74
|
if milliseconds:
|
|
69
|
-
return datetime.strptime(date_str, "%Y-%m-%dT%H:%M:%S.%f")
|
|
75
|
+
return datetime.datetime.strptime(date_str, "%Y-%m-%dT%H:%M:%S.%f")
|
|
70
76
|
else:
|
|
71
|
-
return datetime.strptime(date_str, "%Y-%m-%dT%H:%M:%S")
|
|
77
|
+
return datetime.datetime.strptime(date_str, "%Y-%m-%dT%H:%M:%S")
|
|
72
78
|
|
|
73
79
|
|
|
74
80
|
def get_year_interval(year):
|
|
@@ -76,10 +82,10 @@ def get_year_interval(year):
|
|
|
76
82
|
Get a tuple containing start date and end date for given year.
|
|
77
83
|
"""
|
|
78
84
|
year = int(year)
|
|
79
|
-
if year >
|
|
85
|
+
if year > get_utc_now_datetime().year or year < 2010:
|
|
80
86
|
raise WrongDateFormatException
|
|
81
87
|
|
|
82
|
-
start = datetime(year, 1, 1)
|
|
88
|
+
start = datetime.datetime(year, 1, 1)
|
|
83
89
|
end = start + relativedelta.relativedelta(years=1)
|
|
84
90
|
return start, end
|
|
85
91
|
|
|
@@ -90,10 +96,15 @@ def get_month_interval(year, month):
|
|
|
90
96
|
"""
|
|
91
97
|
year = int(year)
|
|
92
98
|
month = int(month)
|
|
93
|
-
if
|
|
99
|
+
if (
|
|
100
|
+
year > get_utc_now_datetime().year
|
|
101
|
+
or year < 2010
|
|
102
|
+
or month < 1
|
|
103
|
+
or month > 12
|
|
104
|
+
):
|
|
94
105
|
raise WrongDateFormatException
|
|
95
106
|
|
|
96
|
-
start = datetime(year, month, 1)
|
|
107
|
+
start = datetime.datetime(year, month, 1)
|
|
97
108
|
end = start + relativedelta.relativedelta(months=1)
|
|
98
109
|
return start, end
|
|
99
110
|
|
|
@@ -104,7 +115,12 @@ def get_week_interval(year, week):
|
|
|
104
115
|
"""
|
|
105
116
|
year = int(year)
|
|
106
117
|
week = int(week)
|
|
107
|
-
if
|
|
118
|
+
if (
|
|
119
|
+
year > get_utc_now_datetime().year
|
|
120
|
+
or year < 2010
|
|
121
|
+
or week < 1
|
|
122
|
+
or week > 52
|
|
123
|
+
):
|
|
108
124
|
raise WrongDateFormatException
|
|
109
125
|
start = isoweek.Week(year, week).monday()
|
|
110
126
|
end = start + relativedelta.relativedelta(days=7)
|
|
@@ -119,7 +135,7 @@ def get_day_interval(year, month, day):
|
|
|
119
135
|
month = int(month)
|
|
120
136
|
day = int(day)
|
|
121
137
|
if (
|
|
122
|
-
year >
|
|
138
|
+
year > get_utc_now_datetime().year
|
|
123
139
|
or year < 2010
|
|
124
140
|
or month < 1
|
|
125
141
|
or month > 12
|
|
@@ -127,7 +143,7 @@ def get_day_interval(year, month, day):
|
|
|
127
143
|
or day > 31
|
|
128
144
|
):
|
|
129
145
|
raise WrongDateFormatException
|
|
130
|
-
start = datetime(year, month, day)
|
|
146
|
+
start = datetime.datetime(year, month, day)
|
|
131
147
|
end = start + relativedelta.relativedelta(days=1)
|
|
132
148
|
return start, end
|
|
133
149
|
|
|
@@ -147,14 +163,14 @@ def get_business_days(start, end):
|
|
|
147
163
|
Returns the number of business days between two dates.
|
|
148
164
|
"""
|
|
149
165
|
daygenerator = (
|
|
150
|
-
start + timedelta(x + 1) for x in range((end - start).days)
|
|
166
|
+
start + datetime.timedelta(x + 1) for x in range((end - start).days)
|
|
151
167
|
)
|
|
152
168
|
return sum(1 for day in daygenerator if day.weekday() < 5)
|
|
153
169
|
|
|
154
170
|
|
|
155
171
|
def add_business_days_to_date(date, nb_days):
|
|
156
172
|
while nb_days > 0:
|
|
157
|
-
date += timedelta(days=1)
|
|
173
|
+
date += datetime.timedelta(days=1)
|
|
158
174
|
if date.weekday() < 5:
|
|
159
175
|
nb_days -= 1
|
|
160
176
|
return date
|
zou/app/utils/dbhelpers.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from sqlalchemy import create_engine
|
|
1
|
+
from sqlalchemy import create_engine, inspect
|
|
2
2
|
from sqlalchemy_utils import database_exists, create_database
|
|
3
3
|
from sqlalchemy.engine.url import URL
|
|
4
4
|
from sqlalchemy.orm import close_all_sessions
|
|
@@ -39,3 +39,16 @@ def drop_all():
|
|
|
39
39
|
db.session.flush()
|
|
40
40
|
close_all_sessions()
|
|
41
41
|
return db.drop_all()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def is_init():
|
|
45
|
+
"""
|
|
46
|
+
Check if database is initialized.
|
|
47
|
+
"""
|
|
48
|
+
from zou.app import db
|
|
49
|
+
from zou.app.models.project_status import ProjectStatus
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
inspect(db.engine).has_table("person")
|
|
53
|
+
and db.session.query(ProjectStatus).count() == 2
|
|
54
|
+
)
|
zou/app/utils/emails.py
CHANGED
|
@@ -12,9 +12,9 @@ def send_email(subject, html, recipient_email, body=None):
|
|
|
12
12
|
"""
|
|
13
13
|
if body is None:
|
|
14
14
|
body = strip_html_tags(html)
|
|
15
|
-
if app.config["
|
|
15
|
+
if app.config["MAIL_DEBUG_BODY"]:
|
|
16
16
|
print(body)
|
|
17
|
-
|
|
17
|
+
if app.config["MAIL_ENABLED"]:
|
|
18
18
|
with app.app_context():
|
|
19
19
|
try:
|
|
20
20
|
mail_default_sender = app.config["MAIL_DEFAULT_SENDER"]
|
zou/app/utils/fido.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import fido2.features
|
|
2
|
+
|
|
3
|
+
from fido2.webauthn import (
|
|
4
|
+
PublicKeyCredentialRpEntity,
|
|
5
|
+
)
|
|
6
|
+
from fido2.server import Fido2Server
|
|
7
|
+
from urllib.parse import urlparse
|
|
8
|
+
|
|
9
|
+
from zou.app import config
|
|
10
|
+
|
|
11
|
+
fido2.features.webauthn_json_mapping.enabled = True
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_fido_server():
|
|
15
|
+
return Fido2Server(
|
|
16
|
+
PublicKeyCredentialRpEntity(
|
|
17
|
+
name="Kitsu", id=urlparse(f"https://{config.DOMAIN_NAME}").hostname
|
|
18
|
+
),
|
|
19
|
+
verify_origin=(
|
|
20
|
+
None if config.DOMAIN_NAME != "localhost:8080" else lambda a: True
|
|
21
|
+
),
|
|
22
|
+
)
|
zou/app/utils/query.py
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import math
|
|
2
|
+
import orjson as json
|
|
3
|
+
import sqlalchemy.orm as orm
|
|
2
4
|
|
|
3
5
|
from zou.app import app
|
|
4
6
|
from zou.app.utils import fields, string
|
|
5
7
|
from sqlalchemy import func
|
|
8
|
+
from sqlalchemy.inspection import inspect
|
|
6
9
|
|
|
7
10
|
|
|
8
11
|
def get_query_criterions_from_request(request):
|
|
@@ -21,12 +24,57 @@ def apply_criterions_to_db_query(model, db_query, criterions):
|
|
|
21
24
|
"""
|
|
22
25
|
Apply criterions given in HTTP request to the sqlachemy db query object.
|
|
23
26
|
"""
|
|
24
|
-
if "name" in criterions and hasattr(model, "name"):
|
|
25
|
-
value = criterions["name"]
|
|
26
|
-
db_query = db_query.filter(model.name.ilike(value))
|
|
27
|
-
del criterions["name"]
|
|
28
27
|
|
|
29
|
-
|
|
28
|
+
many_join_filter = []
|
|
29
|
+
in_filter = []
|
|
30
|
+
name_filter = []
|
|
31
|
+
filters = {}
|
|
32
|
+
|
|
33
|
+
column_names = inspect(model).all_orm_descriptors.keys()
|
|
34
|
+
for key, value in criterions.items():
|
|
35
|
+
if key not in ["page", "relations"] and key in column_names:
|
|
36
|
+
field_key = getattr(model, key)
|
|
37
|
+
|
|
38
|
+
is_many_to_many_field = hasattr(
|
|
39
|
+
field_key, "property"
|
|
40
|
+
) and isinstance(
|
|
41
|
+
field_key.property, orm.properties.RelationshipProperty
|
|
42
|
+
)
|
|
43
|
+
value_is_list = (
|
|
44
|
+
hasattr(value, "__len__")
|
|
45
|
+
and len(value) > 0
|
|
46
|
+
and value[0] == "["
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
if key == "name" and field_key is not None:
|
|
50
|
+
name_filter.append(value)
|
|
51
|
+
|
|
52
|
+
elif is_many_to_many_field:
|
|
53
|
+
many_join_filter.append((key, value))
|
|
54
|
+
|
|
55
|
+
elif value_is_list:
|
|
56
|
+
value_array = json.loads(value)
|
|
57
|
+
in_filter.append(
|
|
58
|
+
field_key.in_(
|
|
59
|
+
[cast_value(value, field_key) for value in value_array]
|
|
60
|
+
)
|
|
61
|
+
)
|
|
62
|
+
else:
|
|
63
|
+
filters[key] = cast_value(value, field_key)
|
|
64
|
+
|
|
65
|
+
if filters:
|
|
66
|
+
db_query = db_query.filter_by(**filters)
|
|
67
|
+
|
|
68
|
+
for value in name_filter:
|
|
69
|
+
db_query = db_query.filter(model.name.ilike(value))
|
|
70
|
+
|
|
71
|
+
for id_filter in in_filter:
|
|
72
|
+
db_query = db_query.filter(id_filter)
|
|
73
|
+
|
|
74
|
+
for key, value in many_join_filter:
|
|
75
|
+
db_query = db_query.filter(getattr(model, key).any(id=value))
|
|
76
|
+
|
|
77
|
+
return db_query
|
|
30
78
|
|
|
31
79
|
|
|
32
80
|
def get_paginated_results(query, page, limit=None, relations=False):
|
|
@@ -79,7 +127,7 @@ def get_cursor_results(
|
|
|
79
127
|
total = query.count()
|
|
80
128
|
query = (
|
|
81
129
|
query.filter(model.created_at > cursor_created_at)
|
|
82
|
-
.order_by(model.created_at)
|
|
130
|
+
.order_by(model.created_at, model.updated_at, model.id)
|
|
83
131
|
.limit(limit)
|
|
84
132
|
)
|
|
85
133
|
models = fields.serialize_models(
|