zou 0.20.67__py3-none-any.whl → 0.20.69__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.
Files changed (49) hide show
  1. zou/__init__.py +1 -1
  2. zou/app/blueprints/assets/resources.py +134 -91
  3. zou/app/blueprints/auth/resources.py +6 -6
  4. zou/app/blueprints/breakdown/resources.py +47 -47
  5. zou/app/blueprints/chats/resources.py +8 -8
  6. zou/app/blueprints/comments/resources.py +62 -49
  7. zou/app/blueprints/concepts/resources.py +23 -23
  8. zou/app/blueprints/crud/base.py +6 -6
  9. zou/app/blueprints/crud/output_file.py +2 -2
  10. zou/app/blueprints/crud/output_type.py +2 -2
  11. zou/app/blueprints/crud/preview_file.py +2 -2
  12. zou/app/blueprints/crud/software.py +2 -2
  13. zou/app/blueprints/crud/working_file.py +2 -2
  14. zou/app/blueprints/departments/resources.py +20 -20
  15. zou/app/blueprints/edits/resources.py +33 -33
  16. zou/app/blueprints/entities/resources.py +8 -8
  17. zou/app/blueprints/events/resources.py +7 -7
  18. zou/app/blueprints/export/csv/assets.py +2 -2
  19. zou/app/blueprints/export/csv/casting.py +2 -2
  20. zou/app/blueprints/export/csv/edits.py +2 -2
  21. zou/app/blueprints/export/csv/playlists.py +2 -2
  22. zou/app/blueprints/export/csv/shots.py +2 -2
  23. zou/app/blueprints/files/resources.py +123 -123
  24. zou/app/blueprints/news/resources.py +28 -28
  25. zou/app/blueprints/persons/resources.py +75 -75
  26. zou/app/blueprints/playlists/resources.py +37 -37
  27. zou/app/blueprints/previews/resources.py +36 -36
  28. zou/app/blueprints/projects/resources.py +159 -159
  29. zou/app/blueprints/search/resources.py +3 -3
  30. zou/app/blueprints/shots/resources.py +250 -198
  31. zou/app/blueprints/source/csv/assets.py +2 -2
  32. zou/app/blueprints/source/csv/casting.py +2 -2
  33. zou/app/blueprints/source/csv/edits.py +2 -2
  34. zou/app/blueprints/source/csv/shots.py +2 -2
  35. zou/app/blueprints/source/csv/task_type_estimations.py +10 -10
  36. zou/app/blueprints/source/otio.py +6 -6
  37. zou/app/blueprints/source/shotgun/import_errors.py +2 -2
  38. zou/app/blueprints/tasks/resources.py +135 -135
  39. zou/app/blueprints/user/resources.py +87 -87
  40. zou/app/models/attachment_file.py +4 -0
  41. zou/app/services/comments_service.py +41 -9
  42. zou/app/services/tasks_service.py +1 -0
  43. zou/migrations/versions/1b0ab951adca_add_reply_id_to_attachments.py +44 -0
  44. {zou-0.20.67.dist-info → zou-0.20.69.dist-info}/METADATA +5 -5
  45. {zou-0.20.67.dist-info → zou-0.20.69.dist-info}/RECORD +49 -48
  46. {zou-0.20.67.dist-info → zou-0.20.69.dist-info}/WHEEL +0 -0
  47. {zou-0.20.67.dist-info → zou-0.20.69.dist-info}/entry_points.txt +0 -0
  48. {zou-0.20.67.dist-info → zou-0.20.69.dist-info}/licenses/LICENSE +0 -0
  49. {zou-0.20.67.dist-info → zou-0.20.69.dist-info}/top_level.txt +0 -0
@@ -32,8 +32,8 @@ class AssetTasksResource(Resource):
32
32
  name: asset_id
33
33
  required: True
34
34
  type: string
35
- format: UUID
36
- x-example: a24a6ea4-ce75-4665-a070-57453082c25
35
+ format: uuid
36
+ example: a24a6ea4-ce75-4665-a070-57453082c25
37
37
  responses:
38
38
  200:
39
39
  description: Tasks related to given asset for current user
@@ -58,8 +58,8 @@ class AssetTaskTypesResource(Resource):
58
58
  name: asset_id
59
59
  required: True
60
60
  type: string
61
- format: UUID
62
- x-example: a24a6ea4-ce75-4665-a070-57453082c25
61
+ format: uuid
62
+ example: a24a6ea4-ce75-4665-a070-57453082c25
63
63
  responses:
64
64
  200:
65
65
  description: Task types related to given asset for current user
@@ -84,8 +84,8 @@ class ShotTaskTypesResource(Resource):
84
84
  name: shot_id
85
85
  required: True
86
86
  type: string
87
- format: UUID
88
- x-example: a24a6ea4-ce75-4665-a070-57453082c25
87
+ format: uuid
88
+ example: a24a6ea4-ce75-4665-a070-57453082c25
89
89
  responses:
90
90
  200:
91
91
  description: Tasks related to given shot for current user
@@ -110,8 +110,8 @@ class SceneTaskTypesResource(Resource):
110
110
  name: scene_id
111
111
  required: True
112
112
  type: string
113
- format: UUID
114
- x-example: a24a6ea4-ce75-4665-a070-57453082c25
113
+ format: uuid
114
+ example: a24a6ea4-ce75-4665-a070-57453082c25
115
115
  responses:
116
116
  200:
117
117
  description: Tasks related to given scene for current user
@@ -136,8 +136,8 @@ class SequenceTaskTypesResource(Resource):
136
136
  name: sequence_id
137
137
  required: True
138
138
  type: string
139
- format: UUID
140
- x-example: a24a6ea4-ce75-4665-a070-57453082c25
139
+ format: uuid
140
+ example: a24a6ea4-ce75-4665-a070-57453082c25
141
141
  responses:
142
142
  200:
143
143
  description: Tasks related to given sequence for current user
@@ -164,14 +164,14 @@ class AssetTypeAssetsResource(Resource):
164
164
  name: project_id
165
165
  required: True
166
166
  type: string
167
- format: UUID
168
- x-example: a24a6ea4-ce75-4665-a070-57453082c25
167
+ format: uuid
168
+ example: a24a6ea4-ce75-4665-a070-57453082c25
169
169
  - in: path
170
170
  name: asset_type_id
171
171
  required: True
172
172
  type: string
173
- format: UUID
174
- x-example: a24a6ea4-ce75-4665-a070-57453082c25
173
+ format: uuid
174
+ example: a24a6ea4-ce75-4665-a070-57453082c25
175
175
  responses:
176
176
  200:
177
177
  description: Assets of which type is given asset type and are
@@ -222,8 +222,8 @@ class ProjectSequencesResource(Resource):
222
222
  name: project_id
223
223
  required: True
224
224
  type: string
225
- format: UUID
226
- x-example: a24a6ea4-ce75-4665-a070-57453082c25
225
+ format: uuid
226
+ example: a24a6ea4-ce75-4665-a070-57453082c25
227
227
  responses:
228
228
  200:
229
229
  description: Sequences related to given project
@@ -250,8 +250,8 @@ class ProjectEpisodesResource(Resource):
250
250
  name: project_id
251
251
  required: True
252
252
  type: string
253
- format: UUID
254
- x-example: a24a6ea4-ce75-4665-a070-57453082c25
253
+ format: uuid
254
+ example: a24a6ea4-ce75-4665-a070-57453082c25
255
255
  responses:
256
256
  200:
257
257
  description: Episodes related to given project
@@ -278,8 +278,8 @@ class ProjectAssetTypesResource(Resource):
278
278
  name: project_id
279
279
  required: True
280
280
  type: string
281
- format: UUID
282
- x-example: a24a6ea4-ce75-4665-a070-57453082c25
281
+ format: uuid
282
+ example: a24a6ea4-ce75-4665-a070-57453082c25
283
283
  responses:
284
284
  200:
285
285
  description: Asset types related to given project
@@ -306,8 +306,8 @@ class SequenceShotsResource(Resource):
306
306
  name: sequence_id
307
307
  required: True
308
308
  type: string
309
- format: UUID
310
- x-example: a24a6ea4-ce75-4665-a070-57453082c25
309
+ format: uuid
310
+ example: a24a6ea4-ce75-4665-a070-57453082c25
311
311
  responses:
312
312
  200:
313
313
  description: Shots related to given sequence
@@ -334,8 +334,8 @@ class SequenceScenesResource(Resource):
334
334
  name: sequence_id
335
335
  required: True
336
336
  type: string
337
- format: UUID
338
- x-example: a24a6ea4-ce75-4665-a070-57453082c25
337
+ format: uuid
338
+ example: a24a6ea4-ce75-4665-a070-57453082c25
339
339
  responses:
340
340
  200:
341
341
  description: Scenes related to given sequence
@@ -360,8 +360,8 @@ class ShotTasksResource(Resource):
360
360
  name: shot_id
361
361
  required: True
362
362
  type: string
363
- format: UUID
364
- x-example: a24a6ea4-ce75-4665-a070-57453082c25
363
+ format: uuid
364
+ example: a24a6ea4-ce75-4665-a070-57453082c25
365
365
  responses:
366
366
  200:
367
367
  description: Tasks related to given shot
@@ -386,8 +386,8 @@ class SceneTasksResource(Resource):
386
386
  name: scene_id
387
387
  required: True
388
388
  type: string
389
- format: UUID
390
- x-example: a24a6ea4-ce75-4665-a070-57453082c25
389
+ format: uuid
390
+ example: a24a6ea4-ce75-4665-a070-57453082c25
391
391
  responses:
392
392
  200:
393
393
  description: Tasks related to given scene
@@ -412,8 +412,8 @@ class SequenceTasksResource(Resource):
412
412
  name: sequence_id
413
413
  required: True
414
414
  type: string
415
- format: UUID
416
- x-example: a24a6ea4-ce75-4665-a070-57453082c25
415
+ format: uuid
416
+ example: a24a6ea4-ce75-4665-a070-57453082c25
417
417
  responses:
418
418
  200:
419
419
  description: Tasks related to given sequence
@@ -509,7 +509,7 @@ class FiltersResource(Resource, ArgsMixin):
509
509
  name: name
510
510
  required: True
511
511
  type: string
512
- x-example: Name of filter
512
+ example: Name of filter
513
513
  - in: formData
514
514
  name: query
515
515
  required: True
@@ -526,7 +526,7 @@ class FiltersResource(Resource, ArgsMixin):
526
526
  name: project_id
527
527
  required: True
528
528
  type: string
529
- format: UUID
529
+ format: uuid
530
530
  example: a24a6ea4-ce75-4665-a070-57453082c25
531
531
  responses:
532
532
  201:
@@ -580,8 +580,8 @@ class FilterResource(Resource, ArgsMixin):
580
580
  name: filter_id
581
581
  required: True
582
582
  type: string
583
- format: UUID
584
- x-example: a24a6ea4-ce75-4665-a070-57453082c25
583
+ format: uuid
584
+ example: a24a6ea4-ce75-4665-a070-57453082c25
585
585
  responses:
586
586
  200:
587
587
  description: Given filter with updated data.
@@ -613,8 +613,8 @@ class FilterResource(Resource, ArgsMixin):
613
613
  name: filter_id
614
614
  required: True
615
615
  type: string
616
- format: UUID
617
- x-example: a24a6ea4-ce75-4665-a070-57453082c25
616
+ format: uuid
617
+ example: a24a6ea4-ce75-4665-a070-57453082c25
618
618
  responses:
619
619
  204:
620
620
  description: Empty response
@@ -653,7 +653,7 @@ class FilterGroupsResource(Resource, ArgsMixin):
653
653
  name: name
654
654
  required: True
655
655
  type: string
656
- x-example: Name of filter
656
+ example: Name of filter
657
657
  - in: formData
658
658
  name: color
659
659
  required: True
@@ -674,12 +674,12 @@ class FilterGroupsResource(Resource, ArgsMixin):
674
674
  name: project_id
675
675
  required: True
676
676
  type: string
677
- format: UUID
677
+ format: uuid
678
678
  example: a24a6ea4-ce75-4665-a070-57453082c25
679
679
  - in: formData
680
680
  name: department_id
681
681
  required: False
682
- format: UUID
682
+ format: uuid
683
683
  example: a24a6ea4-ce75-4665-a070-57453082c25
684
684
  responses:
685
685
  201:
@@ -744,30 +744,30 @@ class FilterGroupResource(Resource, ArgsMixin):
744
744
  name: filter_id
745
745
  required: True
746
746
  type: string
747
- format: UUID
748
- x-example: a24a6ea4-ce75-4665-a070-57453082c25
747
+ format: uuid
748
+ example: a24a6ea4-ce75-4665-a070-57453082c25
749
749
  - in: formData
750
750
  name: name
751
751
  type: string
752
- x-example: Name of the filter group
752
+ example: Name of the filter group
753
753
  - in: formData
754
754
  name: color
755
755
  type: string
756
- x-example: Color of the filter group
756
+ example: Color of the filter group
757
757
  - in: formData
758
758
  name: is_shared
759
759
  type: boolean
760
- x-example: True
760
+ example: True
761
761
  - in: formData
762
762
  name: project_id
763
763
  type: string
764
- format: UUID
765
- x-example: a24a6ea4-ce75-4665-a070-57453082c25
764
+ format: uuid
765
+ example: a24a6ea4-ce75-4665-a070-57453082c25
766
766
  - in: formData
767
767
  name: department_id
768
768
  type: string
769
- format: UUID
770
- x-example: a24a6ea4-ce75-4665-a070-57453082c25
769
+ format: uuid
770
+ example: a24a6ea4-ce75-4665-a070-57453082c25
771
771
  responses:
772
772
  200:
773
773
  description: Given filter group with updated data
@@ -797,8 +797,8 @@ class FilterGroupResource(Resource, ArgsMixin):
797
797
  name: filter_id
798
798
  required: True
799
799
  type: string
800
- format: UUID
801
- x-example: a24a6ea4-ce75-4665-a070-57453082c25
800
+ format: uuid
801
+ example: a24a6ea4-ce75-4665-a070-57453082c25
802
802
  responses:
803
803
  204:
804
804
  description: Empty response
@@ -839,7 +839,7 @@ class DesktopLoginLogsResource(Resource, ArgsMixin):
839
839
  name: date
840
840
  type: string
841
841
  format: date
842
- x-example: "2022-07-12"
842
+ example: "2022-07-12"
843
843
  responses:
844
844
  201:
845
845
  description: Desktop login log created
@@ -866,12 +866,12 @@ class NotificationsResource(Resource, ArgsMixin):
866
866
  name: after
867
867
  type: string
868
868
  format: date
869
- x-example: "2022-07-12"
869
+ example: "2022-07-12"
870
870
  - in: formData
871
871
  name: before
872
872
  type: string
873
873
  format: date
874
- x-example: "2022-07-12"
874
+ example: "2022-07-12"
875
875
  responses:
876
876
  200:
877
877
  description: 100 last user notifications
@@ -929,8 +929,8 @@ class NotificationResource(Resource, ArgsMixin):
929
929
  name: notification_id
930
930
  required: True
931
931
  type: string
932
- format: UUID
933
- x-example: a24a6ea4-ce75-4665-a070-57453082c25
932
+ format: uuid
933
+ example: a24a6ea4-ce75-4665-a070-57453082c25
934
934
 
935
935
  responses:
936
936
  200:
@@ -949,8 +949,8 @@ class NotificationResource(Resource, ArgsMixin):
949
949
  name: notification_id
950
950
  required: True
951
951
  type: string
952
- format: UUID
953
- x-example: a24a6ea4-ce75-4665-a070-57453082c25
952
+ format: uuid
953
+ example: a24a6ea4-ce75-4665-a070-57453082c25
954
954
  responses:
955
955
  200:
956
956
  description: Notification
@@ -991,8 +991,8 @@ class HasTaskSubscribedResource(Resource):
991
991
  name: task_id
992
992
  required: True
993
993
  type: string
994
- format: UUID
995
- x-example: a24a6ea4-ce75-4665-a070-57453082c25
994
+ format: uuid
995
+ example: a24a6ea4-ce75-4665-a070-57453082c25
996
996
  responses:
997
997
  200:
998
998
  description: True if current user has subscribed to given task,
@@ -1021,8 +1021,8 @@ class TaskSubscribeResource(Resource):
1021
1021
  name: task_id
1022
1022
  required: True
1023
1023
  type: string
1024
- format: UUID
1025
- x-example: a24a6ea4-ce75-4665-a070-57453082c25
1024
+ format: uuid
1025
+ example: a24a6ea4-ce75-4665-a070-57453082c25
1026
1026
  responses:
1027
1027
  201:
1028
1028
  description: Subscription entry created
@@ -1049,8 +1049,8 @@ class TaskUnsubscribeResource(Resource):
1049
1049
  name: task_id
1050
1050
  required: True
1051
1051
  type: string
1052
- format: UUID
1053
- x-example: a24a6ea4-ce75-4665-a070-57453082c25
1052
+ format: uuid
1053
+ example: a24a6ea4-ce75-4665-a070-57453082c25
1054
1054
  responses:
1055
1055
  204:
1056
1056
  description: Subscription entry removed
@@ -1076,14 +1076,14 @@ class HasSequenceSubscribedResource(Resource):
1076
1076
  name: sequence_id
1077
1077
  required: True
1078
1078
  type: string
1079
- format: UUID
1080
- x-example: a24a6ea4-ce75-4665-a070-57453082c25
1079
+ format: uuid
1080
+ example: a24a6ea4-ce75-4665-a070-57453082c25
1081
1081
  - in: path
1082
1082
  name: task_type_id
1083
1083
  required: True
1084
1084
  type: string
1085
- format: UUID
1086
- x-example: a24a6ea4-ce75-4665-a070-57453082c25
1085
+ format: uuid
1086
+ example: a24a6ea4-ce75-4665-a070-57453082c25
1087
1087
  responses:
1088
1088
  200:
1089
1089
  description: True if current user has subscribed to given
@@ -1115,14 +1115,14 @@ class SequenceSubscribeResource(Resource):
1115
1115
  name: sequence_id
1116
1116
  required: True
1117
1117
  type: string
1118
- format: UUID
1119
- x-example: a24a6ea4-ce75-4665-a070-57453082c25
1118
+ format: uuid
1119
+ example: a24a6ea4-ce75-4665-a070-57453082c25
1120
1120
  - in: path
1121
1121
  name: task_type_id
1122
1122
  required: True
1123
1123
  type: string
1124
- format: UUID
1125
- x-example: a24a6ea4-ce75-4665-a070-57453082c25
1124
+ format: uuid
1125
+ example: a24a6ea4-ce75-4665-a070-57453082c25
1126
1126
  responses:
1127
1127
  201:
1128
1128
  description: Subscription entry created
@@ -1150,14 +1150,14 @@ class SequenceUnsubscribeResource(Resource):
1150
1150
  name: sequence_id
1151
1151
  required: True
1152
1152
  type: string
1153
- format: UUID
1154
- x-example: a24a6ea4-ce75-4665-a070-57453082c25
1153
+ format: uuid
1154
+ example: a24a6ea4-ce75-4665-a070-57453082c25
1155
1155
  - in: path
1156
1156
  name: task_type_id
1157
1157
  required: True
1158
1158
  type: string
1159
- format: UUID
1160
- x-example: a24a6ea4-ce75-4665-a070-57453082c25
1159
+ format: uuid
1160
+ example: a24a6ea4-ce75-4665-a070-57453082c25
1161
1161
  responses:
1162
1162
  204:
1163
1163
  description: Subscription entry removed
@@ -1184,14 +1184,14 @@ class SequenceSubscriptionsResource(Resource):
1184
1184
  name: project_id
1185
1185
  required: True
1186
1186
  type: string
1187
- format: UUID
1188
- x-example: a24a6ea4-ce75-4665-a070-57453082c25
1187
+ format: uuid
1188
+ example: a24a6ea4-ce75-4665-a070-57453082c25
1189
1189
  - in: path
1190
1190
  name: task_type_id
1191
1191
  required: True
1192
1192
  type: string
1193
- format: UUID
1194
- x-example: a24a6ea4-ce75-4665-a070-57453082c25
1193
+ format: uuid
1194
+ example: a24a6ea4-ce75-4665-a070-57453082c25
1195
1195
  responses:
1196
1196
  200:
1197
1197
  description: List of sequence ids to which the current user
@@ -1264,14 +1264,14 @@ class TaskTimeSpentResource(Resource):
1264
1264
  name: task_id
1265
1265
  required: True
1266
1266
  type: string
1267
- format: UUID
1268
- x-example: a24a6ea4-ce75-4665-a070-57453082c25
1267
+ format: uuid
1268
+ example: a24a6ea4-ce75-4665-a070-57453082c25
1269
1269
  - in: path
1270
1270
  name: date
1271
1271
  required: True
1272
1272
  type: string
1273
1273
  format: date
1274
- x-example: "2022-07-12"
1274
+ example: "2022-07-12"
1275
1275
  responses:
1276
1276
  200:
1277
1277
  description: Time spents for current user and given date
@@ -1304,7 +1304,7 @@ class DayOffResource(Resource):
1304
1304
  required: True
1305
1305
  type: string
1306
1306
  format: date
1307
- x-example: "2022-07-12"
1307
+ example: "2022-07-12"
1308
1308
  responses:
1309
1309
  200:
1310
1310
  description: Day off object for current user and given date
@@ -1380,8 +1380,8 @@ class JoinChatResource(Resource):
1380
1380
  name: entity_id
1381
1381
  required: True
1382
1382
  type: string
1383
- format: UUID
1384
- x-example: a24a6ea4-ce75-4665-a070-57453082c25
1383
+ format: uuid
1384
+ example: a24a6ea4-ce75-4665-a070-57453082c25
1385
1385
  responses:
1386
1386
  201:
1387
1387
  description: Chat joined
@@ -1403,8 +1403,8 @@ class JoinChatResource(Resource):
1403
1403
  name: entity_id
1404
1404
  required: True
1405
1405
  type: string
1406
- format: UUID
1407
- x-example: a24a6ea4-ce75-4665-a070-57453082c25
1406
+ format: uuid
1407
+ example: a24a6ea4-ce75-4665-a070-57453082c25
1408
1408
  responses:
1409
1409
  204:
1410
1410
  description: empty response
@@ -21,6 +21,10 @@ class AttachmentFile(db.Model, BaseMixin, SerializerMixin):
21
21
  index=True,
22
22
  nullable=True,
23
23
  )
24
+ reply_id = db.Column(
25
+ UUIDType(binary=False),
26
+ nullable=True,
27
+ )
24
28
  chat_message_id = db.Column(
25
29
  UUIDType(binary=False),
26
30
  db.ForeignKey("chat_message.id"),
@@ -19,6 +19,7 @@ from zou.app.services import (
19
19
  assets_service,
20
20
  base_service,
21
21
  breakdown_service,
22
+ deletion_service,
22
23
  entities_service,
23
24
  news_service,
24
25
  notifications_service,
@@ -355,7 +356,7 @@ def reset_mentions(comment):
355
356
  return comment_dict
356
357
 
357
358
 
358
- def create_attachment(comment, uploaded_file, randomize=False):
359
+ def create_attachment(comment, uploaded_file, randomize=False, reply_id=None):
359
360
  tmp_folder = current_app.config["TMP_DIR"]
360
361
  filename = uploaded_file.filename
361
362
  mimetype = uploaded_file.mimetype
@@ -366,11 +367,19 @@ def create_attachment(comment, uploaded_file, randomize=False):
366
367
  filename = f"{filename[:len(filename) - len(extension) - 1]}"
367
368
  filename += f"-{random_str}.{extension}"
368
369
 
370
+ if reply_id is not None:
371
+ is_reply_present = any(
372
+ reply["id"] == reply_id for reply in comment.get("replies", [])
373
+ )
374
+ if not is_reply_present:
375
+ reply_id = None
376
+
369
377
  attachment_file = AttachmentFile.create(
370
378
  name=filename,
371
379
  size=0,
372
380
  extension=extension,
373
381
  mimetype=mimetype,
382
+ reply_id=reply_id,
374
383
  comment_id=comment["id"],
375
384
  )
376
385
  attachment_file_id = str(attachment_file.id)
@@ -455,7 +464,7 @@ def _send_ack_event(project_id, comment, user_id, name="acknowledge"):
455
464
  )
456
465
 
457
466
 
458
- def reply_comment(comment_id, text, person_id=None):
467
+ def reply_comment(comment_id, text, person_id=None, files={}):
459
468
  """
460
469
  Add a reply entry to the JSONB field of given comment model. Create
461
470
  notifications needed for this.
@@ -469,6 +478,7 @@ def reply_comment(comment_id, text, person_id=None):
469
478
  task = tasks_service.get_task(comment.object_id, relations=True)
470
479
  if comment.replies is None:
471
480
  comment.replies = []
481
+
472
482
  reply = {
473
483
  "id": str(fields.gen_uuid()),
474
484
  "date": date_helpers.get_now(),
@@ -483,18 +493,26 @@ def reply_comment(comment_id, text, person_id=None):
483
493
  replies = list(comment.replies)
484
494
  replies.append(reply)
485
495
  comment.update({"replies": replies})
496
+ if len(files.keys()) > 0:
497
+ comment = comment.serialize(relations=True)
498
+ new_attachment_files = add_attachments_to_comment(
499
+ comment, files, reply_id=reply["id"]
500
+ )
501
+ for new_attachment_file in new_attachment_files:
502
+ new_attachment_file["reply_id"] = reply["id"]
503
+ reply["attachment_files"] = new_attachment_files
486
504
  tasks_service.clear_comment_cache(comment_id)
487
505
  events.emit(
488
506
  "comment:reply",
489
507
  {
490
508
  "task_id": task["id"],
491
- "comment_id": str(comment.id),
509
+ "comment_id": comment_id,
492
510
  "reply_id": reply["id"],
493
511
  },
494
512
  project_id=task["project_id"],
495
513
  )
496
514
  notifications_service.create_notifications_for_task_and_reply(
497
- task, comment.serialize(), reply
515
+ task, comment, reply
498
516
  )
499
517
  return reply
500
518
 
@@ -508,6 +526,14 @@ def get_reply(comment_id, reply_id):
508
526
  def delete_reply(comment_id, reply_id):
509
527
  comment = tasks_service.get_comment_raw(comment_id)
510
528
  task = tasks_service.get_task(comment.object_id)
529
+
530
+ if comment.attachment_files is None:
531
+ comment.attachment_files = []
532
+ for attachment_file in comment.attachment_files:
533
+ if attachment_file.reply_id == reply_id:
534
+ deletion_service.remove_attachment_file_by_id(
535
+ attachment_file["id"]
536
+ )
511
537
  if comment.replies is None:
512
538
  comment.replies = []
513
539
  comment.replies = [
@@ -567,19 +593,25 @@ def get_comment_department_mention_ids(project_id, text):
567
593
  ]
568
594
 
569
595
 
570
- def add_attachments_to_comment(comment, files):
596
+ def add_attachments_to_comment(comment, files, reply_id=None):
571
597
  """
572
598
  Create an attachment entry and for each given uploaded files and tie it
573
599
  to given comment.
574
600
  """
575
- comment["attachment_files"] = []
601
+ if comment["attachment_files"] is None:
602
+ comment["attachment_files"] = []
603
+ new_attachment_files = []
576
604
  for uploaded_file in files.values():
577
605
  try:
578
- attachment_file = create_attachment(comment, uploaded_file)
606
+ attachment_file = create_attachment(
607
+ comment, uploaded_file, reply_id=reply_id
608
+ )
579
609
  comment["attachment_files"].append(attachment_file)
610
+ new_attachment_files.append(attachment_file)
580
611
  except IntegrityError:
581
612
  attachment_file = create_attachment(
582
- comment, uploaded_file, randomize=True
613
+ comment, uploaded_file, randomize=True, reply_id=reply_id
583
614
  )
584
615
  comment["attachment_files"].append(attachment_file)
585
- return comment
616
+ new_attachment_files.append(attachment_file)
617
+ return new_attachment_files
@@ -752,6 +752,7 @@ def _build_attachment_map_for_comments(comment_ids):
752
752
  "id": attachment_file_id,
753
753
  "name": attachment_file.name,
754
754
  "extension": attachment_file.extension,
755
+ "reply_id": attachment_file.reply_id,
755
756
  "size": attachment_file.size,
756
757
  }
757
758
  )
@@ -0,0 +1,44 @@
1
+ """add reply id to attachments
2
+
3
+ Revision ID: 1b0ab951adca
4
+ Revises: ce7f46f445dc
5
+ Create Date: 2025-08-27 14:14:16.237272
6
+
7
+ """
8
+ from alembic import op
9
+ import sqlalchemy as sa
10
+ import sqlalchemy_utils
11
+ import sqlalchemy_utils
12
+ import uuid
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision = '1b0ab951adca'
16
+ down_revision = 'ce7f46f445dc'
17
+ branch_labels = None
18
+ depends_on = None
19
+
20
+
21
+ def upgrade():
22
+ # ### commands auto generated by Alembic - please adjust! ###
23
+ with op.batch_alter_table('attachment_file', schema=None) as batch_op:
24
+ batch_op.add_column(sa.Column('reply_id', sqlalchemy_utils.types.uuid.UUIDType(binary=False), default=uuid.uuid4, nullable=True))
25
+
26
+ with op.batch_alter_table('software', schema=None) as batch_op:
27
+ batch_op.alter_column('version',
28
+ existing_type=sa.VARCHAR(length=20),
29
+ nullable=True)
30
+
31
+ # ### end Alembic commands ###
32
+
33
+
34
+ def downgrade():
35
+ # ### commands auto generated by Alembic - please adjust! ###
36
+ with op.batch_alter_table('software', schema=None) as batch_op:
37
+ batch_op.alter_column('version',
38
+ existing_type=sa.VARCHAR(length=20),
39
+ nullable=False)
40
+
41
+ with op.batch_alter_table('attachment_file', schema=None) as batch_op:
42
+ batch_op.drop_column('reply_id')
43
+
44
+ # ### end Alembic commands ###