zou 0.19.14__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} +84 -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/flask.py +1 -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.14.dist-info → zou-0.20.11.dist-info}/METADATA +75 -69
- {zou-0.19.14.dist-info → zou-0.20.11.dist-info}/RECORD +164 -135
- {zou-0.19.14.dist-info → zou-0.20.11.dist-info}/WHEEL +1 -1
- {zou-0.19.14.dist-info → zou-0.20.11.dist-info}/LICENSE +0 -0
- {zou-0.19.14.dist-info → zou-0.20.11.dist-info}/entry_points.txt +0 -0
- {zou-0.19.14.dist-info → zou-0.20.11.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import random
|
|
2
|
+
import string
|
|
3
|
+
|
|
4
|
+
from flask import current_app
|
|
5
|
+
from sqlalchemy.exc import IntegrityError
|
|
6
|
+
|
|
7
|
+
from zou.app.models.attachment_file import AttachmentFile
|
|
8
|
+
from zou.app.models.chat import Chat
|
|
9
|
+
from zou.app.models.chat_message import ChatMessage
|
|
10
|
+
from zou.app.models.entity import Entity
|
|
11
|
+
from zou.app.models.project import Project
|
|
12
|
+
from zou.app.models.project_status import ProjectStatus
|
|
13
|
+
|
|
14
|
+
from zou.app.utils import cache, events, fs, thumbnail
|
|
15
|
+
|
|
16
|
+
from zou.app.stores import file_store
|
|
17
|
+
|
|
18
|
+
from zou.app.services import names_service, persons_service
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def clear_chat_message_cache(chat_message_id):
|
|
22
|
+
cache.cache.delete_memoized(get_chat_message, chat_message_id)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get_chat_raw(entity_id):
|
|
26
|
+
"""
|
|
27
|
+
Return chat corresponding to given entity ID.
|
|
28
|
+
"""
|
|
29
|
+
chat = Chat.get_by(object_id=entity_id)
|
|
30
|
+
if chat is None:
|
|
31
|
+
chat = Chat.create(object_id=entity_id)
|
|
32
|
+
return chat
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def get_chat(entity_id):
|
|
36
|
+
"""
|
|
37
|
+
Return chat corresponding to given entity ID.
|
|
38
|
+
"""
|
|
39
|
+
chat = get_chat_raw(entity_id)
|
|
40
|
+
return chat.serialize(relations=True)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def get_chat_by_id(chat_id):
|
|
44
|
+
"""
|
|
45
|
+
Return chat corresponding to given entity ID.
|
|
46
|
+
"""
|
|
47
|
+
chat = Chat.get(chat_id)
|
|
48
|
+
return chat.serialize(relations=True)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def get_chat_message_raw(chat_message_id):
|
|
52
|
+
"""
|
|
53
|
+
Return chat message corresponding to given chat message ID.
|
|
54
|
+
"""
|
|
55
|
+
return ChatMessage.get(chat_message_id)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@cache.memoize_function(1200)
|
|
59
|
+
def get_chat_message(chat_message_id):
|
|
60
|
+
"""
|
|
61
|
+
Return chat message corresponding to given chat message ID.
|
|
62
|
+
"""
|
|
63
|
+
message = get_chat_message_raw(chat_message_id)
|
|
64
|
+
serialized_message = message.serialize()
|
|
65
|
+
serialized_message["attachment_files"] = []
|
|
66
|
+
for attachment_file in message.attachment_files:
|
|
67
|
+
serialized_message["attachment_files"].append(
|
|
68
|
+
{
|
|
69
|
+
"id": attachment_file.id,
|
|
70
|
+
"name": attachment_file.name,
|
|
71
|
+
"extension": attachment_file.extension,
|
|
72
|
+
}
|
|
73
|
+
)
|
|
74
|
+
return serialized_message
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def join_chat(entity_id, person_id):
|
|
78
|
+
"""
|
|
79
|
+
Join chat for given entity ID.
|
|
80
|
+
"""
|
|
81
|
+
chat = get_chat_raw(entity_id)
|
|
82
|
+
person = persons_service.get_person_raw(person_id)
|
|
83
|
+
chat.participants.append(person)
|
|
84
|
+
chat.save()
|
|
85
|
+
events.emit(
|
|
86
|
+
"chat:joined",
|
|
87
|
+
data={"chat_id": chat.id, "person_id": person.id},
|
|
88
|
+
persist=False,
|
|
89
|
+
)
|
|
90
|
+
return chat.serialize()
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def leave_chat(entity_id, person_id):
|
|
94
|
+
"""
|
|
95
|
+
Leave chat for given entity ID.
|
|
96
|
+
"""
|
|
97
|
+
chat = get_chat_raw(entity_id)
|
|
98
|
+
person = persons_service.get_person_raw(person_id)
|
|
99
|
+
chat.participants.remove(person)
|
|
100
|
+
chat.save()
|
|
101
|
+
events.emit(
|
|
102
|
+
"chat:left",
|
|
103
|
+
data={"chat_id": chat.id, "person_id": person.id},
|
|
104
|
+
persist=False,
|
|
105
|
+
)
|
|
106
|
+
return chat.serialize()
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def get_chat_messages(chat_id):
|
|
110
|
+
"""
|
|
111
|
+
Return chat messages for given chat ID.
|
|
112
|
+
"""
|
|
113
|
+
result = []
|
|
114
|
+
messages = (
|
|
115
|
+
ChatMessage.query.filter(ChatMessage.chat_id == chat_id)
|
|
116
|
+
.order_by(ChatMessage.created_at)
|
|
117
|
+
.all()
|
|
118
|
+
)
|
|
119
|
+
for message in messages:
|
|
120
|
+
serialized_message = message.serialize()
|
|
121
|
+
serialized_message["attachment_files"] = []
|
|
122
|
+
for attachment_file in message.attachment_files:
|
|
123
|
+
serialized_message["attachment_files"].append(
|
|
124
|
+
{
|
|
125
|
+
"id": attachment_file.id,
|
|
126
|
+
"name": attachment_file.name,
|
|
127
|
+
"extension": attachment_file.extension,
|
|
128
|
+
}
|
|
129
|
+
)
|
|
130
|
+
result.append(serialized_message)
|
|
131
|
+
return result
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def get_chat_messages_for_entity(entity_id):
|
|
135
|
+
"""
|
|
136
|
+
Return chat messages for given entity ID.
|
|
137
|
+
"""
|
|
138
|
+
chat = get_chat_raw(entity_id)
|
|
139
|
+
return get_chat_messages(chat.id)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def create_chat_message(chat_id, person_id, message, files=None):
|
|
143
|
+
"""
|
|
144
|
+
Create a new chat message.
|
|
145
|
+
"""
|
|
146
|
+
chat = Chat.get(chat_id)
|
|
147
|
+
message = ChatMessage.create(
|
|
148
|
+
chat_id=chat_id, person_id=person_id, text=message
|
|
149
|
+
)
|
|
150
|
+
chat.update({"last_message": message.created_at})
|
|
151
|
+
serialized_message = message.serialize()
|
|
152
|
+
if files:
|
|
153
|
+
_add_attachments_to_message(serialized_message, files)
|
|
154
|
+
events.emit(
|
|
155
|
+
"chat:new-message",
|
|
156
|
+
data={
|
|
157
|
+
"chat_id": chat_id,
|
|
158
|
+
"chat_message_id": serialized_message["id"],
|
|
159
|
+
"last_message": serialized_message["created_at"],
|
|
160
|
+
},
|
|
161
|
+
persist=False,
|
|
162
|
+
)
|
|
163
|
+
return serialized_message
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def delete_chat_message(chat_message_id):
|
|
167
|
+
"""
|
|
168
|
+
Delete chat message.
|
|
169
|
+
"""
|
|
170
|
+
message = get_chat_message_raw(chat_message_id)
|
|
171
|
+
|
|
172
|
+
for attachment in message.attachment_files:
|
|
173
|
+
attachment_file_id = str(attachment.id)
|
|
174
|
+
file_store.remove_file("attachments", attachment_file_id)
|
|
175
|
+
file_store.remove_picture("thumbnails", attachment_file_id)
|
|
176
|
+
attachment.delete()
|
|
177
|
+
|
|
178
|
+
message.delete()
|
|
179
|
+
events.emit(
|
|
180
|
+
"chat:deleted-message",
|
|
181
|
+
data={
|
|
182
|
+
"chat_id": str(message.chat_id),
|
|
183
|
+
"chat_message_id": chat_message_id,
|
|
184
|
+
},
|
|
185
|
+
persist=False,
|
|
186
|
+
)
|
|
187
|
+
clear_chat_message_cache(chat_message_id)
|
|
188
|
+
return message.serialize()
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def build_participant_filter(person_id):
|
|
192
|
+
"""
|
|
193
|
+
Query filter for returning chats for current user.
|
|
194
|
+
"""
|
|
195
|
+
person = persons_service.get_person_raw(person_id)
|
|
196
|
+
return Chat.participants.contains(person)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def get_chats_for_person(person_id):
|
|
200
|
+
"""
|
|
201
|
+
Return chats for current user.
|
|
202
|
+
"""
|
|
203
|
+
chats = (
|
|
204
|
+
Chat.query.join(Entity, Chat.object_id == Entity.id)
|
|
205
|
+
.join(Project, Entity.project_id == Project.id)
|
|
206
|
+
.join(ProjectStatus, ProjectStatus.id == Project.project_status_id)
|
|
207
|
+
.add_columns(Entity.project_id, Entity.preview_file_id)
|
|
208
|
+
.filter(build_participant_filter(person_id))
|
|
209
|
+
.filter(ProjectStatus.name == "Open")
|
|
210
|
+
.all()
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
result = []
|
|
214
|
+
for chat_model, project_id, preview_file_id in chats:
|
|
215
|
+
chat = chat_model.present()
|
|
216
|
+
chat["entity_name"], _, _ = names_service.get_full_entity_name(
|
|
217
|
+
chat["object_id"]
|
|
218
|
+
)
|
|
219
|
+
chat["project_id"] = project_id
|
|
220
|
+
chat["preview_file_id"] = preview_file_id
|
|
221
|
+
result.append(chat)
|
|
222
|
+
return result
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _add_attachments_to_message(message, files):
|
|
226
|
+
"""
|
|
227
|
+
Create an attachment entry and for each given uploaded files and tie it
|
|
228
|
+
to given message.
|
|
229
|
+
"""
|
|
230
|
+
message["attachment_files"] = []
|
|
231
|
+
for uploaded_file in files.values():
|
|
232
|
+
try:
|
|
233
|
+
attachment_file = _create_attachment(message, uploaded_file)
|
|
234
|
+
message["attachment_files"].append(attachment_file)
|
|
235
|
+
except IntegrityError:
|
|
236
|
+
attachment_file = _create_attachment(
|
|
237
|
+
message, uploaded_file, randomize=True
|
|
238
|
+
)
|
|
239
|
+
message["attachment_files"].append(attachment_file)
|
|
240
|
+
return message
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _create_attachment(message, uploaded_file, randomize=False):
|
|
244
|
+
tmp_folder = current_app.config["TMP_DIR"]
|
|
245
|
+
|
|
246
|
+
# Prepare file name and create db entry
|
|
247
|
+
filename = uploaded_file.filename
|
|
248
|
+
mimetype = uploaded_file.mimetype
|
|
249
|
+
extension = fs.get_file_extension(filename)
|
|
250
|
+
if randomize:
|
|
251
|
+
letters = string.ascii_lowercase
|
|
252
|
+
random_str = "".join(random.choice(letters) for i in range(8))
|
|
253
|
+
filename = f"{filename[:len(filename) - len(extension) - 1]}"
|
|
254
|
+
filename += f"-{random_str}.{extension}"
|
|
255
|
+
attachment_file = AttachmentFile.create(
|
|
256
|
+
name=filename,
|
|
257
|
+
size=0,
|
|
258
|
+
extension=extension,
|
|
259
|
+
mimetype=mimetype,
|
|
260
|
+
chat_message_id=message["id"],
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
# Store attachment file
|
|
264
|
+
attachment_file_id = str(attachment_file.id)
|
|
265
|
+
tmp_file_path = fs.save_file(tmp_folder, attachment_file_id, uploaded_file)
|
|
266
|
+
size = fs.get_file_size(tmp_file_path)
|
|
267
|
+
attachment_file.update({"size": size})
|
|
268
|
+
file_store.add_file("attachments", attachment_file_id, tmp_file_path)
|
|
269
|
+
|
|
270
|
+
# Create thumbnail for pictures
|
|
271
|
+
if "png" in mimetype or "jpg" in mimetype:
|
|
272
|
+
image_path = tmp_file_path
|
|
273
|
+
if "jpg" in mimetype:
|
|
274
|
+
image_path = thumbnail.convert_jpg_to_png(tmp_file_path)
|
|
275
|
+
thumbnail.resize(image_path, (150, 150), True)
|
|
276
|
+
file_store.add_picture("thumbnails", attachment_file_id, image_path)
|
|
277
|
+
fs.rm_file(image_path)
|
|
278
|
+
fs.rm_file(tmp_file_path)
|
|
279
|
+
return attachment_file.present()
|
|
@@ -26,6 +26,7 @@ from zou.app.services import (
|
|
|
26
26
|
projects_service,
|
|
27
27
|
tasks_service,
|
|
28
28
|
concepts_service,
|
|
29
|
+
preview_files_service,
|
|
29
30
|
)
|
|
30
31
|
from zou.app.services.exception import (
|
|
31
32
|
AttachmentFileNotFoundException,
|
|
@@ -33,7 +34,7 @@ from zou.app.services.exception import (
|
|
|
33
34
|
AssetNotFoundException,
|
|
34
35
|
)
|
|
35
36
|
|
|
36
|
-
from zou.app.utils import cache, date_helpers, events, fs, fields
|
|
37
|
+
from zou.app.utils import cache, date_helpers, events, fs, fields, date_helpers
|
|
37
38
|
from zou.app.stores import file_store
|
|
38
39
|
from zou.app import config
|
|
39
40
|
|
|
@@ -69,12 +70,19 @@ def get_attachment_file_path(attachment_file):
|
|
|
69
70
|
|
|
70
71
|
|
|
71
72
|
def create_comment(
|
|
72
|
-
person_id,
|
|
73
|
+
person_id,
|
|
74
|
+
task_id,
|
|
75
|
+
task_status_id,
|
|
76
|
+
text,
|
|
77
|
+
checklist=[],
|
|
78
|
+
files={},
|
|
79
|
+
created_at="",
|
|
80
|
+
links=[],
|
|
73
81
|
):
|
|
74
82
|
"""
|
|
75
83
|
Create a new comment and related: news, notifications and events.
|
|
76
84
|
"""
|
|
77
|
-
task = tasks_service.
|
|
85
|
+
task = tasks_service.get_task(task_id, relations=True)
|
|
78
86
|
task_status = tasks_service.get_task_status(task_status_id)
|
|
79
87
|
author = _get_comment_author(person_id)
|
|
80
88
|
_check_retake_capping(task_status, task)
|
|
@@ -87,6 +95,7 @@ def create_comment(
|
|
|
87
95
|
text=text,
|
|
88
96
|
checklist=checklist,
|
|
89
97
|
created_at=created_at,
|
|
98
|
+
links=links,
|
|
90
99
|
)
|
|
91
100
|
task, status_changed = _manage_status_change(task_status, task, comment)
|
|
92
101
|
_manage_subscriptions(task, comment, status_changed)
|
|
@@ -127,41 +136,51 @@ def _get_comment_author(person_id):
|
|
|
127
136
|
|
|
128
137
|
|
|
129
138
|
def _manage_status_change(task_status, task, comment):
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
"
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
139
|
+
is_last_comment = (
|
|
140
|
+
task["last_comment_date"] is None
|
|
141
|
+
or task["last_comment_date"] <= comment["created_at"]
|
|
142
|
+
)
|
|
143
|
+
if not is_last_comment:
|
|
144
|
+
status_changed = False
|
|
145
|
+
task = tasks_service.reset_task_data(task["id"])
|
|
146
|
+
else:
|
|
147
|
+
status_changed = task_status["id"] != task["task_status_id"]
|
|
148
|
+
new_data = {
|
|
149
|
+
"task_status_id": task_status["id"],
|
|
150
|
+
"last_comment_date": comment["created_at"],
|
|
151
|
+
}
|
|
152
|
+
if status_changed:
|
|
153
|
+
if task_status["is_retake"]:
|
|
154
|
+
retake_count = task["retake_count"]
|
|
155
|
+
if retake_count is None or retake_count == "NoneType":
|
|
156
|
+
retake_count = 0
|
|
157
|
+
new_data["retake_count"] = retake_count + 1
|
|
158
|
+
|
|
159
|
+
if task_status["is_feedback_request"]:
|
|
160
|
+
new_data["end_date"] = date_helpers.get_utc_now_datetime()
|
|
161
|
+
|
|
162
|
+
if (
|
|
163
|
+
task_status["short_name"] == "wip"
|
|
164
|
+
and task["real_start_date"] is None
|
|
165
|
+
):
|
|
166
|
+
new_data["real_start_date"] = datetime.datetime.now(
|
|
167
|
+
datetime.timezone.utc
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
tasks_service.update_task(task["id"], new_data)
|
|
171
|
+
|
|
172
|
+
if status_changed:
|
|
173
|
+
events.emit(
|
|
174
|
+
"task:status-changed",
|
|
175
|
+
{
|
|
176
|
+
"task_id": task["id"],
|
|
177
|
+
"new_task_status_id": new_data["task_status_id"],
|
|
178
|
+
"previous_task_status_id": task["task_status_id"],
|
|
179
|
+
"person_id": comment["person_id"],
|
|
180
|
+
},
|
|
181
|
+
project_id=task["project_id"],
|
|
182
|
+
)
|
|
183
|
+
task.update(new_data)
|
|
165
184
|
return task, status_changed
|
|
166
185
|
|
|
167
186
|
|
|
@@ -182,12 +201,11 @@ def _manage_subscriptions(task, comment, status_changed):
|
|
|
182
201
|
|
|
183
202
|
|
|
184
203
|
def _run_status_automation(automation, task, person_id):
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
)
|
|
190
|
-
if not is_automation_to_run:
|
|
204
|
+
if (
|
|
205
|
+
automation["archived"]
|
|
206
|
+
or task["task_type_id"] != automation["in_task_type_id"]
|
|
207
|
+
or task["task_status_id"] != automation["in_task_status_id"]
|
|
208
|
+
):
|
|
191
209
|
return
|
|
192
210
|
|
|
193
211
|
priorities = projects_service.get_task_type_priority_map(
|
|
@@ -215,7 +233,7 @@ def _run_status_automation(automation, task, person_id):
|
|
|
215
233
|
task_status = tasks_service.get_task_status(
|
|
216
234
|
automation["in_task_status_id"]
|
|
217
235
|
)
|
|
218
|
-
create_comment(
|
|
236
|
+
new_comment = create_comment(
|
|
219
237
|
person_id,
|
|
220
238
|
task_to_update["id"],
|
|
221
239
|
automation["out_task_status_id"],
|
|
@@ -228,6 +246,31 @@ def _run_status_automation(automation, task, person_id):
|
|
|
228
246
|
{},
|
|
229
247
|
None,
|
|
230
248
|
)
|
|
249
|
+
if automation["import_last_revision"]:
|
|
250
|
+
preview_file = (
|
|
251
|
+
preview_files_service.get_last_preview_file_for_task(
|
|
252
|
+
task["id"]
|
|
253
|
+
)
|
|
254
|
+
)
|
|
255
|
+
preview_files = (
|
|
256
|
+
preview_files_service.get_preview_files_for_revision(
|
|
257
|
+
preview_file["task_id"], preview_file["revision"]
|
|
258
|
+
)
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
for preview_file in preview_files:
|
|
262
|
+
new_preview_file = (
|
|
263
|
+
tasks_service.add_preview_file_to_comment(
|
|
264
|
+
new_comment["id"],
|
|
265
|
+
new_comment["person_id"],
|
|
266
|
+
task_to_update["id"],
|
|
267
|
+
)
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
preview_files_service.copy_preview_file_in_another_one(
|
|
271
|
+
preview_file["id"], new_preview_file["id"]
|
|
272
|
+
)
|
|
273
|
+
|
|
231
274
|
elif automation["out_field_type"] == "ready_for":
|
|
232
275
|
try:
|
|
233
276
|
asset = assets_service.update_asset(
|
|
@@ -248,6 +291,7 @@ def new_comment(
|
|
|
248
291
|
files={},
|
|
249
292
|
checklist=[],
|
|
250
293
|
created_at="",
|
|
294
|
+
links=[],
|
|
251
295
|
):
|
|
252
296
|
"""
|
|
253
297
|
Create a new comment for given object (by default, it considers this object
|
|
@@ -262,8 +306,8 @@ def new_comment(
|
|
|
262
306
|
)
|
|
263
307
|
except ValueError:
|
|
264
308
|
try:
|
|
265
|
-
created_at_date =
|
|
266
|
-
created_at
|
|
309
|
+
created_at_date = date_helpers.get_datetime_from_string(
|
|
310
|
+
created_at
|
|
267
311
|
)
|
|
268
312
|
except ValueError:
|
|
269
313
|
pass
|
|
@@ -278,6 +322,7 @@ def new_comment(
|
|
|
278
322
|
checklist=checklist,
|
|
279
323
|
text=text,
|
|
280
324
|
created_at=created_at_date,
|
|
325
|
+
links=links,
|
|
281
326
|
)
|
|
282
327
|
|
|
283
328
|
comment = comment.serialize(relations=True)
|
|
@@ -290,24 +335,6 @@ def new_comment(
|
|
|
290
335
|
return comment
|
|
291
336
|
|
|
292
337
|
|
|
293
|
-
def add_attachments_to_comment(comment, files):
|
|
294
|
-
"""
|
|
295
|
-
Create an attachment entry and for each given uploaded files and tie it
|
|
296
|
-
to given comment.
|
|
297
|
-
"""
|
|
298
|
-
comment["attachment_files"] = []
|
|
299
|
-
for uploaded_file in files.values():
|
|
300
|
-
try:
|
|
301
|
-
attachment_file = create_attachment(comment, uploaded_file)
|
|
302
|
-
comment["attachment_files"].append(attachment_file)
|
|
303
|
-
except IntegrityError:
|
|
304
|
-
attachment_file = create_attachment(
|
|
305
|
-
comment, uploaded_file, randomize=True
|
|
306
|
-
)
|
|
307
|
-
comment["attachment_files"].append(attachment_file)
|
|
308
|
-
return comment
|
|
309
|
-
|
|
310
|
-
|
|
311
338
|
def reset_mentions(comment):
|
|
312
339
|
task = tasks_service.get_task(comment["object_id"])
|
|
313
340
|
mentions = get_comment_mentions(task["project_id"], comment["text"])
|
|
@@ -536,3 +563,21 @@ def get_comment_department_mention_ids(project_id, text):
|
|
|
536
563
|
str(mention.id)
|
|
537
564
|
for mention in get_comment_department_mentions(project_id, text)
|
|
538
565
|
]
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
def add_attachments_to_comment(comment, files):
|
|
569
|
+
"""
|
|
570
|
+
Create an attachment entry and for each given uploaded files and tie it
|
|
571
|
+
to given comment.
|
|
572
|
+
"""
|
|
573
|
+
comment["attachment_files"] = []
|
|
574
|
+
for uploaded_file in files.values():
|
|
575
|
+
try:
|
|
576
|
+
attachment_file = create_attachment(comment, uploaded_file)
|
|
577
|
+
comment["attachment_files"].append(attachment_file)
|
|
578
|
+
except IntegrityError:
|
|
579
|
+
attachment_file = create_attachment(
|
|
580
|
+
comment, uploaded_file, randomize=True
|
|
581
|
+
)
|
|
582
|
+
comment["attachment_files"].append(attachment_file)
|
|
583
|
+
return comment
|
|
@@ -33,7 +33,7 @@ from zou.app.services.exception import (
|
|
|
33
33
|
|
|
34
34
|
def clear_concept_cache(concept_id):
|
|
35
35
|
cache.cache.delete_memoized(get_concept, concept_id)
|
|
36
|
-
cache.cache.delete_memoized(
|
|
36
|
+
cache.cache.delete_memoized(get_concept, concept_id, True)
|
|
37
37
|
cache.cache.delete_memoized(get_full_concept, concept_id)
|
|
38
38
|
|
|
39
39
|
|
|
@@ -61,23 +61,15 @@ def get_concept_raw(concept_id):
|
|
|
61
61
|
|
|
62
62
|
|
|
63
63
|
@cache.memoize_function(120)
|
|
64
|
-
def
|
|
64
|
+
def get_concept(concept_id, relations=False):
|
|
65
65
|
"""
|
|
66
66
|
Return given concept as a dictionary.
|
|
67
67
|
"""
|
|
68
68
|
return get_concept_raw(concept_id).serialize(
|
|
69
|
-
obj_type="Concept", relations=
|
|
69
|
+
obj_type="Concept", relations=relations
|
|
70
70
|
)
|
|
71
71
|
|
|
72
72
|
|
|
73
|
-
@cache.memoize_function(120)
|
|
74
|
-
def get_concept(concept_id):
|
|
75
|
-
"""
|
|
76
|
-
Return given concept as a dictionary.
|
|
77
|
-
"""
|
|
78
|
-
return get_concept_raw(concept_id).serialize(obj_type="Concept")
|
|
79
|
-
|
|
80
|
-
|
|
81
73
|
@cache.memoize_function(120)
|
|
82
74
|
def get_full_concept(concept_id):
|
|
83
75
|
"""
|
|
@@ -86,7 +78,7 @@ def get_full_concept(concept_id):
|
|
|
86
78
|
concepts = get_concepts_and_tasks({"id": concept_id})
|
|
87
79
|
if len(concepts) > 0:
|
|
88
80
|
concept = concepts[0]
|
|
89
|
-
concept.update(
|
|
81
|
+
concept.update(get_concept(concept_id, relations=True))
|
|
90
82
|
return concept
|
|
91
83
|
else:
|
|
92
84
|
raise ConceptNotFoundException
|