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
zou/app/services/user_service.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from sqlalchemy.orm import aliased
|
|
2
|
-
from sqlalchemy import func
|
|
2
|
+
from sqlalchemy import func, or_
|
|
3
3
|
|
|
4
4
|
from zou.app import config
|
|
5
5
|
from zou.app.models.comment import Comment
|
|
@@ -9,6 +9,7 @@ from zou.app.models.notification import Notification
|
|
|
9
9
|
from zou.app.models.person import Person
|
|
10
10
|
from zou.app.models.project import Project
|
|
11
11
|
from zou.app.models.project_status import ProjectStatus
|
|
12
|
+
from zou.app.models.subscription import Subscription
|
|
12
13
|
from zou.app.models.search_filter import SearchFilter
|
|
13
14
|
from zou.app.models.search_filter_group import SearchFilterGroup
|
|
14
15
|
from zou.app.models.task import Task
|
|
@@ -32,16 +33,23 @@ from zou.app.services.exception import (
|
|
|
32
33
|
SearchFilterNotFoundException,
|
|
33
34
|
SearchFilterGroupNotFoundException,
|
|
34
35
|
NotificationNotFoundException,
|
|
36
|
+
WrongParameterException,
|
|
35
37
|
)
|
|
36
38
|
from zou.app.utils import cache, fields, permissions
|
|
37
39
|
|
|
38
40
|
|
|
39
|
-
def clear_filter_cache(user_id):
|
|
40
|
-
|
|
41
|
+
def clear_filter_cache(user_id=None):
|
|
42
|
+
if user_id is None:
|
|
43
|
+
cache.cache.delete_memoized(get_user_filters)
|
|
44
|
+
else:
|
|
45
|
+
cache.cache.delete_memoized(get_user_filters, user_id)
|
|
41
46
|
|
|
42
47
|
|
|
43
|
-
def clear_filter_group_cache(user_id):
|
|
44
|
-
|
|
48
|
+
def clear_filter_group_cache(user_id=None):
|
|
49
|
+
if user_id is None:
|
|
50
|
+
cache.cache.delete_memoized(get_user_filter_groups)
|
|
51
|
+
else:
|
|
52
|
+
cache.cache.delete_memoized(get_user_filter_groups, user_id)
|
|
45
53
|
|
|
46
54
|
|
|
47
55
|
def clear_project_cache():
|
|
@@ -402,20 +410,27 @@ def check_belong_to_project(project_id):
|
|
|
402
410
|
if project_id is None:
|
|
403
411
|
return False
|
|
404
412
|
|
|
405
|
-
project = projects_service.
|
|
413
|
+
project = projects_service.get_project(str(project_id), relations=True)
|
|
406
414
|
current_user = persons_service.get_current_user()
|
|
407
415
|
return current_user["id"] in project["team"]
|
|
408
416
|
|
|
409
417
|
|
|
410
|
-
def
|
|
418
|
+
def has_project_access(project_id):
|
|
411
419
|
"""
|
|
412
420
|
Return true if current user is a manager or has a task assigned for this
|
|
413
421
|
project.
|
|
414
422
|
"""
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
or check_belong_to_project(project_id)
|
|
423
|
+
return permissions.has_admin_permissions() or check_belong_to_project(
|
|
424
|
+
project_id
|
|
418
425
|
)
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def check_project_access(project_id):
|
|
429
|
+
"""
|
|
430
|
+
Return true if current user is a manager or has a task assigned for this
|
|
431
|
+
project. Raise a PermissionDenied exception if not.
|
|
432
|
+
"""
|
|
433
|
+
is_allowed = has_project_access(project_id)
|
|
419
434
|
if not is_allowed:
|
|
420
435
|
raise permissions.PermissionDenied
|
|
421
436
|
return is_allowed
|
|
@@ -463,6 +478,16 @@ def check_task_status_access(task_status_id):
|
|
|
463
478
|
return True
|
|
464
479
|
|
|
465
480
|
|
|
481
|
+
def check_task_access(task_id):
|
|
482
|
+
"""
|
|
483
|
+
Return true if current user can have access to a task.
|
|
484
|
+
"""
|
|
485
|
+
task = tasks_service.get_task(task_id)
|
|
486
|
+
check_project_access(task["project_id"])
|
|
487
|
+
check_entity_access(task["entity_id"])
|
|
488
|
+
return True
|
|
489
|
+
|
|
490
|
+
|
|
466
491
|
def check_comment_access(comment_id):
|
|
467
492
|
"""
|
|
468
493
|
Return true if current user can have access to a comment.
|
|
@@ -501,15 +526,23 @@ def check_comment_access(comment_id):
|
|
|
501
526
|
return True
|
|
502
527
|
|
|
503
528
|
|
|
504
|
-
def
|
|
529
|
+
def has_manager_project_access(project_id):
|
|
505
530
|
"""
|
|
506
531
|
Return true if current user is a manager and has a task assigned for this
|
|
507
532
|
project.
|
|
508
533
|
"""
|
|
509
|
-
|
|
534
|
+
return permissions.has_admin_permissions() or (
|
|
510
535
|
permissions.has_manager_permissions()
|
|
511
536
|
and check_belong_to_project(project_id)
|
|
512
537
|
)
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
def check_manager_project_access(project_id):
|
|
541
|
+
"""
|
|
542
|
+
Return true if current user is a manager and has a task assigned for this
|
|
543
|
+
project. Raise a PermissionDenied exception if not.
|
|
544
|
+
"""
|
|
545
|
+
is_allowed = has_manager_project_access(project_id)
|
|
513
546
|
if not is_allowed:
|
|
514
547
|
raise permissions.PermissionDenied
|
|
515
548
|
return is_allowed
|
|
@@ -572,7 +605,7 @@ def check_supervisor_task_access(task, new_data={}):
|
|
|
572
605
|
# checks that the supervisor only modifies columns
|
|
573
606
|
# for which he is authorized
|
|
574
607
|
allowed_columns = set(
|
|
575
|
-
["priority", "start_date", "due_date", "estimation"]
|
|
608
|
+
["priority", "start_date", "due_date", "estimation", "difficulty"]
|
|
576
609
|
)
|
|
577
610
|
if len(set(new_data.keys()) - allowed_columns) == 0:
|
|
578
611
|
user_departments = persons_service.get_current_user(
|
|
@@ -692,7 +725,7 @@ def check_metadata_department_access(entity, new_data={}):
|
|
|
692
725
|
return is_allowed
|
|
693
726
|
|
|
694
727
|
|
|
695
|
-
def
|
|
728
|
+
def check_task_department_access(task_id, person_id):
|
|
696
729
|
"""
|
|
697
730
|
Return true if current user is an admin or is a manager and is in team
|
|
698
731
|
or is a supervisor in the department of the task or is an artist assigning
|
|
@@ -744,7 +777,7 @@ def check_person_is_not_bot(person_id):
|
|
|
744
777
|
return True
|
|
745
778
|
|
|
746
779
|
|
|
747
|
-
def
|
|
780
|
+
def check_task_department_access_for_unassign(task_id, person_id=None):
|
|
748
781
|
"""
|
|
749
782
|
Return true if current user is an admin or is a manager and is in team
|
|
750
783
|
or is a supervisor in the department of the task or is an artist assigning
|
|
@@ -852,43 +885,86 @@ def get_user_filters(current_user_id):
|
|
|
852
885
|
result = {}
|
|
853
886
|
|
|
854
887
|
filters = (
|
|
855
|
-
SearchFilter.query.
|
|
856
|
-
.
|
|
857
|
-
.filter(
|
|
858
|
-
|
|
888
|
+
SearchFilter.query.outerjoin(Project)
|
|
889
|
+
.outerjoin(ProjectStatus)
|
|
890
|
+
.filter(
|
|
891
|
+
or_(
|
|
892
|
+
SearchFilter.person_id == current_user_id,
|
|
893
|
+
SearchFilter.is_shared == True,
|
|
894
|
+
)
|
|
895
|
+
)
|
|
896
|
+
.filter(
|
|
897
|
+
or_(build_open_project_filter(), SearchFilter.project_id == None)
|
|
898
|
+
)
|
|
859
899
|
.all()
|
|
860
900
|
)
|
|
861
901
|
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
+ SearchFilter.query.filter(SearchFilter.person_id == current_user_id)
|
|
865
|
-
.filter(SearchFilter.project_id == None)
|
|
866
|
-
.all()
|
|
867
|
-
)
|
|
902
|
+
current_user = persons_service.get_current_user(relations=True)
|
|
903
|
+
is_manager = permissions.has_manager_permissions()
|
|
868
904
|
|
|
869
905
|
for search_filter in filters:
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
906
|
+
department_id = search_filter.department_id
|
|
907
|
+
is_in_departments = (
|
|
908
|
+
department_id is not None
|
|
909
|
+
and str(department_id) in current_user["departments"]
|
|
910
|
+
)
|
|
873
911
|
|
|
874
|
-
if
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
912
|
+
if department_id is None or is_manager or is_in_departments:
|
|
913
|
+
if search_filter.list_type not in result:
|
|
914
|
+
result[search_filter.list_type] = {}
|
|
915
|
+
subresult = result[search_filter.list_type]
|
|
878
916
|
|
|
879
|
-
|
|
880
|
-
|
|
917
|
+
if search_filter.project_id is None:
|
|
918
|
+
project_id = "all"
|
|
919
|
+
else:
|
|
920
|
+
project_id = str(search_filter.project_id)
|
|
921
|
+
|
|
922
|
+
if project_id not in subresult:
|
|
923
|
+
subresult[project_id] = []
|
|
881
924
|
|
|
882
|
-
|
|
925
|
+
subresult[project_id].append(search_filter.serialize())
|
|
883
926
|
|
|
884
927
|
return result
|
|
885
928
|
|
|
886
929
|
|
|
887
|
-
def create_filter(
|
|
930
|
+
def create_filter(
|
|
931
|
+
list_type,
|
|
932
|
+
name,
|
|
933
|
+
query,
|
|
934
|
+
project_id=None,
|
|
935
|
+
entity_type=None,
|
|
936
|
+
is_shared=False,
|
|
937
|
+
search_filter_group_id=None,
|
|
938
|
+
department_id=None,
|
|
939
|
+
):
|
|
888
940
|
"""
|
|
889
941
|
Add a new search filter to the database.
|
|
890
942
|
"""
|
|
891
943
|
current_user = persons_service.get_current_user()
|
|
944
|
+
|
|
945
|
+
if project_id is None or (
|
|
946
|
+
project_id is not None and not has_manager_project_access(project_id)
|
|
947
|
+
):
|
|
948
|
+
is_shared = False
|
|
949
|
+
|
|
950
|
+
if search_filter_group_id is not None:
|
|
951
|
+
search_filter_group = SearchFilterGroup.get_by(
|
|
952
|
+
id=search_filter_group_id
|
|
953
|
+
)
|
|
954
|
+
if search_filter_group is None:
|
|
955
|
+
raise SearchFilterGroupNotFoundException
|
|
956
|
+
if is_shared != search_filter_group.is_shared:
|
|
957
|
+
raise WrongParameterException(
|
|
958
|
+
"A search filter should have the same value for is_shared than its search filter group."
|
|
959
|
+
)
|
|
960
|
+
|
|
961
|
+
if department_id is not None:
|
|
962
|
+
department = tasks_service.get_department(department_id)
|
|
963
|
+
if department is None:
|
|
964
|
+
raise WrongParameterException(
|
|
965
|
+
f"No department found with id: {department_id}"
|
|
966
|
+
)
|
|
967
|
+
|
|
892
968
|
search_filter = SearchFilter.create(
|
|
893
969
|
list_type=list_type,
|
|
894
970
|
name=name,
|
|
@@ -896,9 +972,15 @@ def create_filter(list_type, name, query, project_id=None, entity_type=None):
|
|
|
896
972
|
project_id=project_id,
|
|
897
973
|
person_id=current_user["id"],
|
|
898
974
|
entity_type=entity_type,
|
|
975
|
+
is_shared=is_shared,
|
|
976
|
+
search_filter_group_id=search_filter_group_id,
|
|
977
|
+
department_id=department_id,
|
|
899
978
|
)
|
|
900
979
|
search_filter.serialize()
|
|
901
|
-
|
|
980
|
+
if search_filter.is_shared:
|
|
981
|
+
clear_filter_cache()
|
|
982
|
+
else:
|
|
983
|
+
clear_filter_cache(current_user["id"])
|
|
902
984
|
return search_filter.serialize()
|
|
903
985
|
|
|
904
986
|
|
|
@@ -910,10 +992,64 @@ def update_filter(search_filter_id, data):
|
|
|
910
992
|
search_filter = SearchFilter.get_by(
|
|
911
993
|
id=search_filter_id, person_id=current_user["id"]
|
|
912
994
|
)
|
|
995
|
+
if current_user["role"] == "admin" and search_filter is None:
|
|
996
|
+
search_filter = SearchFilter.get_by(id=search_filter_id)
|
|
997
|
+
|
|
913
998
|
if search_filter is None:
|
|
914
999
|
raise SearchFilterNotFoundException
|
|
1000
|
+
|
|
1001
|
+
department_id = data.get("department_id", None)
|
|
1002
|
+
if department_id is not None:
|
|
1003
|
+
department = tasks_service.get_department(department_id)
|
|
1004
|
+
if department is None:
|
|
1005
|
+
raise WrongParameterException(
|
|
1006
|
+
f"No department found with id: {department_id}"
|
|
1007
|
+
)
|
|
1008
|
+
|
|
1009
|
+
if (
|
|
1010
|
+
data.get("is_shared", None) is not None
|
|
1011
|
+
and search_filter.is_shared != data["is_shared"]
|
|
1012
|
+
and (
|
|
1013
|
+
data.get("project_id", None) is None
|
|
1014
|
+
or (
|
|
1015
|
+
data["project_id"] is not None
|
|
1016
|
+
and not has_manager_project_access(data["project_id"])
|
|
1017
|
+
)
|
|
1018
|
+
)
|
|
1019
|
+
):
|
|
1020
|
+
data["is_shared"] = False
|
|
1021
|
+
|
|
1022
|
+
if (
|
|
1023
|
+
search_filter_group_id := data.get(
|
|
1024
|
+
"search_filter_group_id", search_filter.search_filter_group_id
|
|
1025
|
+
)
|
|
1026
|
+
) is not None:
|
|
1027
|
+
search_filter_group = SearchFilterGroup.get_by(
|
|
1028
|
+
id=search_filter_group_id
|
|
1029
|
+
)
|
|
1030
|
+
if search_filter_group is None:
|
|
1031
|
+
raise SearchFilterGroupNotFoundException
|
|
1032
|
+
if (
|
|
1033
|
+
data.get("is_shared", search_filter.is_shared)
|
|
1034
|
+
!= search_filter_group.is_shared
|
|
1035
|
+
):
|
|
1036
|
+
raise WrongParameterException(
|
|
1037
|
+
"A search filter should have the same value for is_shared than its search filter group."
|
|
1038
|
+
)
|
|
1039
|
+
|
|
1040
|
+
department_id = data.get("department_id", None)
|
|
1041
|
+
if department_id is not None:
|
|
1042
|
+
department = tasks_service.get_department(department_id)
|
|
1043
|
+
if department is None:
|
|
1044
|
+
raise WrongParameterException(
|
|
1045
|
+
f"No department found with id: {department_id}"
|
|
1046
|
+
)
|
|
1047
|
+
|
|
915
1048
|
search_filter.update(data)
|
|
916
|
-
|
|
1049
|
+
if search_filter.is_shared:
|
|
1050
|
+
clear_filter_cache()
|
|
1051
|
+
else:
|
|
1052
|
+
clear_filter_cache(current_user["id"])
|
|
917
1053
|
return search_filter.serialize()
|
|
918
1054
|
|
|
919
1055
|
|
|
@@ -928,7 +1064,10 @@ def remove_filter(search_filter_id):
|
|
|
928
1064
|
if search_filter is None:
|
|
929
1065
|
raise SearchFilterNotFoundException
|
|
930
1066
|
search_filter.delete()
|
|
931
|
-
|
|
1067
|
+
if search_filter.is_shared:
|
|
1068
|
+
clear_filter_cache()
|
|
1069
|
+
else:
|
|
1070
|
+
clear_filter_cache(current_user["id"])
|
|
932
1071
|
return search_filter.serialize()
|
|
933
1072
|
|
|
934
1073
|
|
|
@@ -942,7 +1081,7 @@ def get_filter_groups():
|
|
|
942
1081
|
return get_user_filter_groups(current_user["id"])
|
|
943
1082
|
|
|
944
1083
|
|
|
945
|
-
@cache.memoize_function(
|
|
1084
|
+
@cache.memoize_function(10)
|
|
946
1085
|
def get_user_filter_groups(current_user_id):
|
|
947
1086
|
"""
|
|
948
1087
|
Retrieve search filter groups used for given user. It groups them by
|
|
@@ -953,49 +1092,74 @@ def get_user_filter_groups(current_user_id):
|
|
|
953
1092
|
result = {}
|
|
954
1093
|
|
|
955
1094
|
filter_groups = (
|
|
956
|
-
SearchFilterGroup.query.
|
|
1095
|
+
SearchFilterGroup.query.outerjoin(
|
|
957
1096
|
Project, Project.id == SearchFilterGroup.project_id
|
|
958
1097
|
)
|
|
959
|
-
.
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
.
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
+ SearchFilterGroup.query.filter(
|
|
968
|
-
SearchFilterGroup.person_id == current_user_id
|
|
1098
|
+
.outerjoin(
|
|
1099
|
+
ProjectStatus, ProjectStatus.id == Project.project_status_id
|
|
1100
|
+
)
|
|
1101
|
+
.filter(
|
|
1102
|
+
or_(
|
|
1103
|
+
SearchFilterGroup.person_id == current_user_id,
|
|
1104
|
+
SearchFilterGroup.is_shared == True,
|
|
1105
|
+
)
|
|
969
1106
|
)
|
|
970
|
-
.filter(
|
|
1107
|
+
.filter(or_(build_open_project_filter(), Project.id == None))
|
|
971
1108
|
.all()
|
|
972
1109
|
)
|
|
973
1110
|
|
|
1111
|
+
current_user = persons_service.get_current_user(relations=True)
|
|
1112
|
+
is_manager = permissions.has_manager_permissions()
|
|
1113
|
+
|
|
974
1114
|
for search_filter_group in filter_groups:
|
|
975
1115
|
if search_filter_group.list_type not in result:
|
|
976
1116
|
result[search_filter_group.list_type] = {}
|
|
977
|
-
subresult = result[search_filter_group.list_type]
|
|
978
1117
|
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
1118
|
+
department_id = search_filter_group.department_id
|
|
1119
|
+
is_in_departments = (
|
|
1120
|
+
department_id is not None
|
|
1121
|
+
and str(department_id) in current_user["departments"]
|
|
1122
|
+
)
|
|
1123
|
+
if department_id is None or is_manager or is_in_departments:
|
|
1124
|
+
subresult = result[search_filter_group.list_type]
|
|
983
1125
|
|
|
984
|
-
|
|
985
|
-
|
|
1126
|
+
if search_filter_group.project_id is None:
|
|
1127
|
+
project_id = "all"
|
|
1128
|
+
else:
|
|
1129
|
+
project_id = str(search_filter_group.project_id)
|
|
986
1130
|
|
|
987
|
-
|
|
1131
|
+
if project_id not in subresult:
|
|
1132
|
+
subresult[project_id] = []
|
|
1133
|
+
subresult[project_id].append(search_filter_group.serialize())
|
|
988
1134
|
|
|
989
1135
|
return result
|
|
990
1136
|
|
|
991
1137
|
|
|
992
1138
|
def create_filter_group(
|
|
993
|
-
list_type,
|
|
1139
|
+
list_type,
|
|
1140
|
+
name,
|
|
1141
|
+
color,
|
|
1142
|
+
project_id=None,
|
|
1143
|
+
entity_type=None,
|
|
1144
|
+
is_shared=False,
|
|
1145
|
+
department_id=None,
|
|
994
1146
|
):
|
|
995
1147
|
"""
|
|
996
1148
|
Add a new search filter group to the database.
|
|
997
1149
|
"""
|
|
998
1150
|
current_user = persons_service.get_current_user()
|
|
1151
|
+
if project_id is None or (
|
|
1152
|
+
project_id is not None and not has_manager_project_access(project_id)
|
|
1153
|
+
):
|
|
1154
|
+
is_shared = False
|
|
1155
|
+
|
|
1156
|
+
if department_id is not None:
|
|
1157
|
+
department = tasks_service.get_department(department_id)
|
|
1158
|
+
if department is None:
|
|
1159
|
+
raise WrongParameterException(
|
|
1160
|
+
f"No department found with id: {department_id}"
|
|
1161
|
+
)
|
|
1162
|
+
|
|
999
1163
|
search_filter_group = SearchFilterGroup.create(
|
|
1000
1164
|
list_type=list_type,
|
|
1001
1165
|
name=name,
|
|
@@ -1003,9 +1167,16 @@ def create_filter_group(
|
|
|
1003
1167
|
project_id=project_id,
|
|
1004
1168
|
person_id=current_user["id"],
|
|
1005
1169
|
entity_type=entity_type,
|
|
1170
|
+
is_shared=is_shared,
|
|
1171
|
+
department_id=department_id,
|
|
1006
1172
|
)
|
|
1007
1173
|
search_filter_group.serialize()
|
|
1008
|
-
|
|
1174
|
+
|
|
1175
|
+
if search_filter_group.is_shared:
|
|
1176
|
+
clear_filter_group_cache()
|
|
1177
|
+
else:
|
|
1178
|
+
clear_filter_group_cache(current_user["id"])
|
|
1179
|
+
|
|
1009
1180
|
return search_filter_group.serialize()
|
|
1010
1181
|
|
|
1011
1182
|
|
|
@@ -1030,10 +1201,43 @@ def update_filter_group(search_filter_group_id, data):
|
|
|
1030
1201
|
search_filter_group = SearchFilterGroup.get_by(
|
|
1031
1202
|
id=search_filter_group_id, person_id=current_user["id"]
|
|
1032
1203
|
)
|
|
1204
|
+
|
|
1033
1205
|
if search_filter_group is None:
|
|
1034
1206
|
raise SearchFilterGroupNotFoundException
|
|
1207
|
+
|
|
1208
|
+
if (
|
|
1209
|
+
data.get("is_shared", None) is not None
|
|
1210
|
+
and search_filter_group.is_shared != data["is_shared"]
|
|
1211
|
+
and (
|
|
1212
|
+
data.get("project_id", None) is None
|
|
1213
|
+
or (
|
|
1214
|
+
data["project_id"] is not None
|
|
1215
|
+
and not has_manager_project_access(data["project_id"])
|
|
1216
|
+
)
|
|
1217
|
+
)
|
|
1218
|
+
):
|
|
1219
|
+
data["is_shared"] = False
|
|
1220
|
+
|
|
1035
1221
|
search_filter_group.update(data)
|
|
1036
|
-
|
|
1222
|
+
|
|
1223
|
+
if (
|
|
1224
|
+
data.get("is_shared", None) is not None
|
|
1225
|
+
and data.get("project_id", None) is not None
|
|
1226
|
+
and has_manager_project_access(data["project_id"])
|
|
1227
|
+
):
|
|
1228
|
+
if (
|
|
1229
|
+
SearchFilter.query.filter_by(
|
|
1230
|
+
search_filter_group_id=search_filter_group_id
|
|
1231
|
+
).update({"is_shared": data["is_shared"]})
|
|
1232
|
+
> 0
|
|
1233
|
+
):
|
|
1234
|
+
SearchFilter.query.session.commit()
|
|
1235
|
+
clear_filter_cache()
|
|
1236
|
+
|
|
1237
|
+
if search_filter_group.is_shared:
|
|
1238
|
+
clear_filter_group_cache()
|
|
1239
|
+
else:
|
|
1240
|
+
clear_filter_group_cache(current_user["id"])
|
|
1037
1241
|
return search_filter_group.serialize()
|
|
1038
1242
|
|
|
1039
1243
|
|
|
@@ -1047,8 +1251,19 @@ def remove_filter_group(search_filter_group_id):
|
|
|
1047
1251
|
)
|
|
1048
1252
|
if search_filter_group is None:
|
|
1049
1253
|
raise SearchFilterGroupNotFoundException
|
|
1254
|
+
if (
|
|
1255
|
+
SearchFilter.query.filter_by(
|
|
1256
|
+
search_filter_group_id=search_filter_group_id
|
|
1257
|
+
).delete()
|
|
1258
|
+
> 0
|
|
1259
|
+
):
|
|
1260
|
+
SearchFilter.query.session.commit()
|
|
1261
|
+
clear_filter_cache()
|
|
1050
1262
|
search_filter_group.delete()
|
|
1051
|
-
|
|
1263
|
+
if search_filter_group.is_shared:
|
|
1264
|
+
clear_filter_group_cache()
|
|
1265
|
+
else:
|
|
1266
|
+
clear_filter_group_cache(current_user["id"])
|
|
1052
1267
|
return search_filter_group.serialize()
|
|
1053
1268
|
|
|
1054
1269
|
|
|
@@ -1064,6 +1279,18 @@ def get_notification(notification_id):
|
|
|
1064
1279
|
return notifications[0]
|
|
1065
1280
|
|
|
1066
1281
|
|
|
1282
|
+
def update_notification(notification_id, read):
|
|
1283
|
+
"""
|
|
1284
|
+
Update read status of given notification.
|
|
1285
|
+
"""
|
|
1286
|
+
current_user = persons_service.get_current_user()
|
|
1287
|
+
notification = Notification.get_by(
|
|
1288
|
+
id=notification_id, person_id=current_user["id"]
|
|
1289
|
+
)
|
|
1290
|
+
notification.update({"read": read})
|
|
1291
|
+
return notification.serialize()
|
|
1292
|
+
|
|
1293
|
+
|
|
1067
1294
|
def get_unread_notifications_count(notification_id=None):
|
|
1068
1295
|
"""
|
|
1069
1296
|
Return the number of unread notifications.
|
|
@@ -1074,6 +1301,9 @@ def get_unread_notifications_count(notification_id=None):
|
|
|
1074
1301
|
).count()
|
|
1075
1302
|
|
|
1076
1303
|
|
|
1304
|
+
from sqlalchemy import and_, func
|
|
1305
|
+
|
|
1306
|
+
|
|
1077
1307
|
def get_last_notifications(
|
|
1078
1308
|
notification_id=None,
|
|
1079
1309
|
after=None,
|
|
@@ -1081,6 +1311,8 @@ def get_last_notifications(
|
|
|
1081
1311
|
task_type_id=None,
|
|
1082
1312
|
task_status_id=None,
|
|
1083
1313
|
notification_type=None,
|
|
1314
|
+
read=None,
|
|
1315
|
+
watching=None,
|
|
1084
1316
|
):
|
|
1085
1317
|
"""
|
|
1086
1318
|
Return last 100 user notifications.
|
|
@@ -1095,6 +1327,13 @@ def get_last_notifications(
|
|
|
1095
1327
|
.join(Author, Author.id == Notification.author_id)
|
|
1096
1328
|
.join(Task, Task.id == Notification.task_id)
|
|
1097
1329
|
.join(Project, Project.id == Task.project_id)
|
|
1330
|
+
.outerjoin(
|
|
1331
|
+
Subscription,
|
|
1332
|
+
and_(
|
|
1333
|
+
Subscription.task_id == Task.id,
|
|
1334
|
+
Subscription.person_id == current_user["id"],
|
|
1335
|
+
),
|
|
1336
|
+
)
|
|
1098
1337
|
.outerjoin(Comment, Comment.id == Notification.comment_id)
|
|
1099
1338
|
.add_columns(
|
|
1100
1339
|
Project.id,
|
|
@@ -1105,6 +1344,7 @@ def get_last_notifications(
|
|
|
1105
1344
|
Comment.text,
|
|
1106
1345
|
Comment.replies,
|
|
1107
1346
|
Task.entity_id,
|
|
1347
|
+
Subscription.id,
|
|
1108
1348
|
Author.role,
|
|
1109
1349
|
)
|
|
1110
1350
|
)
|
|
@@ -1133,6 +1373,15 @@ def get_last_notifications(
|
|
|
1133
1373
|
if notification_type is not None:
|
|
1134
1374
|
query = query.filter(Notification.type == notification_type)
|
|
1135
1375
|
|
|
1376
|
+
if read is not None:
|
|
1377
|
+
query = query.filter(Notification.read == read)
|
|
1378
|
+
|
|
1379
|
+
if watching is not None:
|
|
1380
|
+
if watching:
|
|
1381
|
+
query = query.filter(Subscription.id != None)
|
|
1382
|
+
else:
|
|
1383
|
+
query = query.filter(Subscription.id == None)
|
|
1384
|
+
|
|
1136
1385
|
notifications = query.limit(100).all()
|
|
1137
1386
|
|
|
1138
1387
|
for (
|
|
@@ -1145,10 +1394,11 @@ def get_last_notifications(
|
|
|
1145
1394
|
comment_text,
|
|
1146
1395
|
comment_replies,
|
|
1147
1396
|
task_entity_id,
|
|
1397
|
+
subscription_id,
|
|
1148
1398
|
role,
|
|
1149
1399
|
) in notifications:
|
|
1150
|
-
(full_entity_name, episode_id) =
|
|
1151
|
-
task_entity_id
|
|
1400
|
+
(full_entity_name, episode_id, entity_preview_file_id) = (
|
|
1401
|
+
names_service.get_full_entity_name(task_entity_id)
|
|
1152
1402
|
)
|
|
1153
1403
|
preview_file_id = None
|
|
1154
1404
|
mentions = []
|
|
@@ -1208,6 +1458,8 @@ def get_last_notifications(
|
|
|
1208
1458
|
"change": notification.change,
|
|
1209
1459
|
"full_entity_name": full_entity_name,
|
|
1210
1460
|
"episode_id": episode_id,
|
|
1461
|
+
"entity_preview_file_id": entity_preview_file_id,
|
|
1462
|
+
"subscription_id": subscription_id,
|
|
1211
1463
|
}
|
|
1212
1464
|
)
|
|
1213
1465
|
)
|
|
@@ -1220,17 +1472,21 @@ def mark_notifications_as_read():
|
|
|
1220
1472
|
Mark all recent notifications for current_user as read. It is useful
|
|
1221
1473
|
to mark a list of notifications as read after an user retrieved them.
|
|
1222
1474
|
"""
|
|
1475
|
+
from sqlalchemy import update
|
|
1476
|
+
from zou.app import db
|
|
1477
|
+
|
|
1223
1478
|
current_user = persons_service.get_current_user()
|
|
1224
|
-
|
|
1225
|
-
Notification
|
|
1226
|
-
.
|
|
1227
|
-
.
|
|
1479
|
+
update_stmt = (
|
|
1480
|
+
update(Notification)
|
|
1481
|
+
.where(Notification.person_id == current_user["id"])
|
|
1482
|
+
.where(Notification.read == False)
|
|
1483
|
+
.values(read=True)
|
|
1228
1484
|
)
|
|
1229
1485
|
|
|
1230
|
-
|
|
1231
|
-
|
|
1486
|
+
db.session.execute(update_stmt)
|
|
1487
|
+
db.session.commit()
|
|
1232
1488
|
|
|
1233
|
-
return
|
|
1489
|
+
return True
|
|
1234
1490
|
|
|
1235
1491
|
|
|
1236
1492
|
def has_task_subscription(task_id):
|
|
@@ -1313,38 +1569,25 @@ def get_timezone():
|
|
|
1313
1569
|
|
|
1314
1570
|
|
|
1315
1571
|
def get_context():
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
search_filter_groups = get_filter_groups()
|
|
1334
|
-
preview_background_files = files_service.get_preview_background_files()
|
|
1335
|
-
|
|
1336
|
-
return {
|
|
1337
|
-
"asset_types": asset_types,
|
|
1338
|
-
"custom_actions": custom_actions,
|
|
1339
|
-
"status_automations": status_automations,
|
|
1340
|
-
"departments": departments,
|
|
1341
|
-
"notification_count": notification_count,
|
|
1342
|
-
"persons": persons,
|
|
1343
|
-
"project_status": project_status_list,
|
|
1344
|
-
"projects": projects,
|
|
1345
|
-
"task_types": task_types,
|
|
1346
|
-
"task_status": task_status_list,
|
|
1347
|
-
"search_filters": search_filters,
|
|
1348
|
-
"search_filter_groups": search_filter_groups,
|
|
1349
|
-
"preview_background_files": preview_background_files,
|
|
1572
|
+
context = {
|
|
1573
|
+
"asset_types": assets_service.get_asset_types(),
|
|
1574
|
+
"custom_actions": custom_actions_service.get_custom_actions(),
|
|
1575
|
+
"status_automations": status_automations_service.get_status_automations(),
|
|
1576
|
+
"departments": tasks_service.get_departments(),
|
|
1577
|
+
"studios": tasks_service.get_studios(),
|
|
1578
|
+
"notification_count": get_unread_notifications_count(),
|
|
1579
|
+
"persons": persons_service.get_persons(
|
|
1580
|
+
minimal=not permissions.has_manager_permissions()
|
|
1581
|
+
),
|
|
1582
|
+
"project_status": projects_service.get_project_statuses(),
|
|
1583
|
+
"projects": get_open_projects(),
|
|
1584
|
+
"task_types": tasks_service.get_task_types(),
|
|
1585
|
+
"task_status": tasks_service.get_task_statuses(),
|
|
1586
|
+
"search_filters": get_filters(),
|
|
1587
|
+
"search_filter_groups": get_filter_groups(),
|
|
1588
|
+
"preview_background_files": files_service.get_preview_background_files(),
|
|
1350
1589
|
}
|
|
1590
|
+
|
|
1591
|
+
if permissions.has_admin_permissions():
|
|
1592
|
+
context["user_limit"] = config.USER_LIMIT
|
|
1593
|
+
return context
|