zou 0.20.38__py3-none-any.whl → 0.20.40__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 (42) hide show
  1. zou/__init__.py +1 -1
  2. zou/app/api.py +38 -43
  3. zou/app/blueprints/crud/__init__.py +10 -0
  4. zou/app/blueprints/crud/budget.py +21 -0
  5. zou/app/blueprints/crud/budget_entry.py +15 -0
  6. zou/app/blueprints/crud/plugin.py +13 -0
  7. zou/app/blueprints/crud/salary_scale.py +73 -0
  8. zou/app/blueprints/playlists/resources.py +15 -0
  9. zou/app/blueprints/projects/__init__.py +17 -0
  10. zou/app/blueprints/projects/resources.py +402 -7
  11. zou/app/blueprints/shots/resources.py +1 -0
  12. zou/app/mixin.py +12 -1
  13. zou/app/models/budget.py +39 -0
  14. zou/app/models/budget_entry.py +65 -0
  15. zou/app/models/person.py +17 -1
  16. zou/app/models/plugin.py +21 -0
  17. zou/app/models/salary_scale.py +28 -0
  18. zou/app/services/budget_service.py +195 -0
  19. zou/app/services/comments_service.py +1 -1
  20. zou/app/services/exception.py +8 -0
  21. zou/app/services/plugins_service.py +169 -0
  22. zou/app/services/user_service.py +1 -3
  23. zou/app/utils/commands.py +51 -1
  24. zou/app/utils/fields.py +10 -0
  25. zou/app/utils/plugins.py +88 -0
  26. zou/cli.py +169 -1
  27. zou/event_stream.py +23 -5
  28. zou/migrations/versions/2762a797f1f9_add_people_salary_information.py +52 -0
  29. zou/migrations/versions/45f739ef962a_add_people_salary_scale_table.py +70 -0
  30. zou/migrations/versions/4aab1f84ad72_introduce_plugin_table.py +68 -0
  31. zou/migrations/versions/7a16258f2fab_add_currency_field_to_budgets.py +33 -0
  32. zou/migrations/versions/83e2f33a9b14_add_project_bugdet_table.py +57 -0
  33. zou/migrations/versions/8ab98c178903_add_budget_entry_table.py +123 -0
  34. zou/migrations/versions/d25118cddcaa_modify_salary_scale_model.py +133 -0
  35. zou/plugin_template/__init__.py +39 -0
  36. zou/plugin_template/routes.py +6 -0
  37. {zou-0.20.38.dist-info → zou-0.20.40.dist-info}/METADATA +7 -3
  38. {zou-0.20.38.dist-info → zou-0.20.40.dist-info}/RECORD +42 -22
  39. {zou-0.20.38.dist-info → zou-0.20.40.dist-info}/WHEEL +1 -1
  40. {zou-0.20.38.dist-info → zou-0.20.40.dist-info}/entry_points.txt +0 -0
  41. {zou-0.20.38.dist-info → zou-0.20.40.dist-info}/licenses/LICENSE +0 -0
  42. {zou-0.20.38.dist-info → zou-0.20.40.dist-info}/top_level.txt +0 -0
zou/cli.py CHANGED
@@ -9,12 +9,13 @@ import traceback
9
9
  from sqlalchemy.exc import IntegrityError
10
10
 
11
11
  from zou.app.utils import dbhelpers, auth, commands
12
- from zou.app.services import persons_service, auth_service
12
+ from zou.app.services import persons_service, auth_service, plugins_service
13
13
  from zou.app.services.exception import (
14
14
  IsUserLimitReachedException,
15
15
  PersonNotFoundException,
16
16
  TwoFactorAuthenticationNotEnabledException,
17
17
  )
18
+
18
19
  from zou.app import app, config
19
20
 
20
21
  from zou import __file__ as root_path
@@ -643,5 +644,172 @@ def renormalize_movie_preview_files(
643
644
  )
644
645
 
645
646
 
647
+ @cli.command()
648
+ @click.option(
649
+ "--path",
650
+ required=True,
651
+ )
652
+ @click.option(
653
+ "--force",
654
+ is_flag=True,
655
+ default=False,
656
+ show_default=True,
657
+ )
658
+ def install_plugin(path, force=False):
659
+ """
660
+ Install a plugin.
661
+ """
662
+ with app.app_context():
663
+ plugins_service.install_plugin(path, force)
664
+ print(f"Plugin {path} installed. Restart the server to apply changes.")
665
+
666
+
667
+ @cli.command()
668
+ @click.option(
669
+ "--id",
670
+ required=True,
671
+ )
672
+ def uninstall_plugin(id):
673
+ """
674
+ Uninstall a plugin.
675
+ """
676
+ with app.app_context():
677
+ plugins_service.uninstall_plugin(id)
678
+ print(f"Plugin {id} uninstalled.")
679
+
680
+
681
+ @cli.command()
682
+ @click.option(
683
+ "--path",
684
+ required=True,
685
+ )
686
+ @click.option(
687
+ "--id",
688
+ help="Plugin ID (must be unique).",
689
+ required=True,
690
+ )
691
+ @click.option(
692
+ "--name", help="Plugin name.", default="MyPlugin", show_default=True
693
+ )
694
+ @click.option(
695
+ "--description",
696
+ help="Plugin description.",
697
+ default="My plugin description.",
698
+ show_default=True,
699
+ )
700
+ @click.option(
701
+ "--version", help="Plugin version.", default="0.1.0", show_default=True
702
+ )
703
+ @click.option(
704
+ "--maintainer",
705
+ help="Plugin maintainer.",
706
+ default="Author <author@author.com>",
707
+ show_default=True,
708
+ )
709
+ @click.option(
710
+ "--website",
711
+ help="Plugin website.",
712
+ default="mywebsite.com",
713
+ show_default=True,
714
+ )
715
+ @click.option(
716
+ "--license",
717
+ help="Plugin license.",
718
+ default="GPL-3.0-only",
719
+ show_default=True,
720
+ )
721
+ @click.option(
722
+ "--force",
723
+ is_flag=True,
724
+ default=False,
725
+ show_default=True,
726
+ )
727
+ def create_plugin_skeleton(
728
+ path,
729
+ id,
730
+ name,
731
+ description,
732
+ version,
733
+ maintainer,
734
+ website,
735
+ license,
736
+ force=False,
737
+ ):
738
+ """
739
+ Create a plugin skeleton.
740
+ """
741
+ plugin_path = plugins_service.create_plugin_skeleton(
742
+ path,
743
+ id,
744
+ name,
745
+ description,
746
+ version,
747
+ maintainer,
748
+ website,
749
+ license,
750
+ force,
751
+ )
752
+ print(f"Plugin skeleton created in '{plugin_path}'.")
753
+
754
+
755
+ @cli.command()
756
+ @click.option(
757
+ "--path",
758
+ required=True,
759
+ )
760
+ @click.option(
761
+ "--output-path",
762
+ required=True,
763
+ )
764
+ @click.option(
765
+ "--force",
766
+ is_flag=True,
767
+ default=False,
768
+ show_default=True,
769
+ )
770
+ def create_plugin_package(
771
+ path,
772
+ output_path,
773
+ force=False,
774
+ ):
775
+ """
776
+ Create a plugin package.
777
+ """
778
+ plugin_path = plugins_service.create_plugin_package(
779
+ path, output_path, force
780
+ )
781
+ print(f"Plugin package created in '{plugin_path}'.")
782
+
783
+
784
+ @cli.command()
785
+ @click.option(
786
+ "--format",
787
+ "output_format",
788
+ type=click.Choice(["table", "json"], case_sensitive=False),
789
+ default="table",
790
+ show_default=True,
791
+ help="Output format: table or json.",
792
+ )
793
+ @click.option(
794
+ "--verbose",
795
+ is_flag=True,
796
+ default=False,
797
+ help="Show more plugin information.",
798
+ )
799
+ @click.option(
800
+ "--filter-field",
801
+ type=click.Choice(
802
+ ["plugin_id", "name", "maintainer", "license"], case_sensitive=False
803
+ ),
804
+ help="Field to filter by.",
805
+ )
806
+ @click.option("--filter-value", type=str, help="Value to search in the field.")
807
+ def list_plugins(output_format, verbose, filter_field, filter_value):
808
+ """
809
+ List installed plugins.
810
+ """
811
+ commands.list_plugins(output_format, verbose, filter_field, filter_value)
812
+
813
+
646
814
  if __name__ == "__main__":
647
815
  cli()
zou/event_stream.py CHANGED
@@ -35,6 +35,9 @@ def _get_empty_room(current_frame=0):
35
35
  "current_preview_file_index": None,
36
36
  "current_frame": current_frame,
37
37
  "is_repeating": None,
38
+ "is_annotations_displayed": False,
39
+ "is_zoom_enabled": False,
40
+ "is_waveform_displayed": False,
38
41
  "is_laser_mode": None,
39
42
  "handle_in": None,
40
43
  "handle_out": None,
@@ -59,7 +62,7 @@ def _leave_room(room_id, user_id):
59
62
  room["people"] = list(set(room["people"]) - {user_id})
60
63
  if len(room["people"]) > 0:
61
64
  rooms_data[room_id] = room
62
- else:
65
+ elif room_id in rooms_data:
63
66
  del rooms_data[room_id]
64
67
  _emit_people_updated(room_id, room["people"])
65
68
  return room
@@ -81,10 +84,17 @@ def _update_room_playing_status(data, room):
81
84
  room["is_playing"] = data.get("is_playing", False)
82
85
  room["is_repeating"] = data.get("is_repeating", False)
83
86
  room["is_laser_mode"] = data.get("is_laser_mode", False)
87
+ room["is_annotations_displayed"] = data.get(
88
+ "is_annotations_displayed", False
89
+ )
90
+ room["is_zoom_enabled"] = data.get("is_zoom_enabled", False)
91
+ room["is_waveform_displayed"] = data.get("is_waveform_displayed", False)
84
92
  room["current_entity_id"] = data.get("current_entity_id", None)
85
93
  room["current_entity_index"] = data.get("current_entity_index", None)
86
94
  room["current_preview_file_id"] = data.get("current_preview_file_id", None)
87
- room["current_preview_file_index"] = data.get("current_preview_file_index", None)
95
+ room["current_preview_file_index"] = data.get(
96
+ "current_preview_file_index", None
97
+ )
88
98
  room["handle_in"] = data.get("handle_in", None)
89
99
  room["handle_out"] = data.get("handle_out", None)
90
100
  if "current_frame" in data:
@@ -193,19 +203,20 @@ def on_join(data):
193
203
  room["playlist_id"] = room_id
194
204
  rooms_data[room_id] = room
195
205
  _emit_people_updated(room_id, room["people"])
206
+ emit("preview-room:room-updated", room, room=room_id)
196
207
 
197
208
 
198
209
  @socketio.on("preview-room:leave", namespace="/events")
199
210
  @jwt_required()
200
211
  def on_leave(data):
201
212
  user_id = get_jwt_identity()
202
- room_id = data["playlist_id"]
213
+ room_id = data.get("playlist_id", "")
203
214
  _leave_room(room_id, user_id)
204
215
 
205
216
 
206
- @socketio.on("preview-room:update-playing-status", namespace="/events")
217
+ @socketio.on("preview-room:room-updated", namespace="/events")
207
218
  @jwt_required()
208
- def on_playing_status_updated(data, only_newcomer=False):
219
+ def on_room_updated(data, only_newcomer=False):
209
220
  room, room_id = _get_room_from_data(data)
210
221
  rooms_data[room_id] = _update_room_playing_status(data, room)
211
222
  event_data = {"only_newcomer": only_newcomer, **rooms_data[room_id]}
@@ -240,6 +251,13 @@ def on_change_version(data):
240
251
  emit("preview-room:change-version", data, room=room_id)
241
252
 
242
253
 
254
+ @socketio.on("preview-room:panzoom-changed", namespace="/events")
255
+ @jwt_required()
256
+ def on_change_version(data):
257
+ room_id = data["playlist_id"]
258
+ emit("preview-room:panzoom-changed", data, room=room_id)
259
+
260
+
243
261
  if __name__ == "__main__":
244
262
  socketio.run(
245
263
  app,
@@ -0,0 +1,52 @@
1
+ """add people salary information
2
+
3
+ Revision ID: 2762a797f1f9
4
+ Revises: 307edd8c639d
5
+ Create Date: 2025-04-01 18:02:39.343857
6
+
7
+ """
8
+
9
+ from alembic import op
10
+ import sqlalchemy as sa
11
+ import sqlalchemy_utils
12
+
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision = "2762a797f1f9"
16
+ down_revision = "307edd8c639d"
17
+ branch_labels = None
18
+ depends_on = None
19
+
20
+ from zou.app.models.person import POSITION_TYPES, SENIORITY_TYPES
21
+
22
+
23
+ def upgrade():
24
+ with op.batch_alter_table("person", schema=None) as batch_op:
25
+ batch_op.add_column(
26
+ sa.Column(
27
+ "position",
28
+ sqlalchemy_utils.types.choice.ChoiceType(POSITION_TYPES),
29
+ nullable=True,
30
+ )
31
+ )
32
+ batch_op.add_column(
33
+ sa.Column(
34
+ "seniority",
35
+ sqlalchemy_utils.types.choice.ChoiceType(SENIORITY_TYPES),
36
+ nullable=True,
37
+ )
38
+ )
39
+ batch_op.add_column(
40
+ sa.Column("daily_salary", sa.Integer(), nullable=True)
41
+ )
42
+
43
+ # ### end Alembic commands ###
44
+
45
+
46
+ def downgrade():
47
+ # ### commands auto generated by Alembic - please adjust! ###
48
+ with op.batch_alter_table("person", schema=None) as batch_op:
49
+ batch_op.drop_column("daily_salary")
50
+ batch_op.drop_column("seniority")
51
+ batch_op.drop_column("position")
52
+ # ### end Alembic commands ###
@@ -0,0 +1,70 @@
1
+ """add people salary scale table
2
+
3
+ Revision ID: 45f739ef962a
4
+ Revises: 2762a797f1f9
5
+ Create Date: 2025-04-02 10:11:12.544243
6
+
7
+ """
8
+
9
+ from alembic import op
10
+ import sqlalchemy as sa
11
+ import sqlalchemy_utils
12
+ import sqlalchemy_utils
13
+ import uuid
14
+
15
+ # revision identifiers, used by Alembic.
16
+ revision = "45f739ef962a"
17
+ down_revision = "2762a797f1f9"
18
+ branch_labels = None
19
+ depends_on = None
20
+
21
+
22
+ def upgrade():
23
+ # ### commands auto generated by Alembic - please adjust! ###
24
+ op.create_table(
25
+ "salary_scale",
26
+ sa.Column(
27
+ "department_id",
28
+ sqlalchemy_utils.types.uuid.UUIDType(binary=False),
29
+ default=uuid.uuid4,
30
+ nullable=False,
31
+ ),
32
+ sa.Column("senior_supervisor_salary", sa.Integer(), nullable=False),
33
+ sa.Column("mid_supervisor_salary", sa.Integer(), nullable=False),
34
+ sa.Column("junior_supervisor_salary", sa.Integer(), nullable=False),
35
+ sa.Column("senior_lead_salary", sa.Integer(), nullable=False),
36
+ sa.Column("mid_lead_salary", sa.Integer(), nullable=False),
37
+ sa.Column("junior_lead_salary", sa.Integer(), nullable=False),
38
+ sa.Column("senior_artist_salary", sa.Integer(), nullable=False),
39
+ sa.Column("mid_artist_salary", sa.Integer(), nullable=False),
40
+ sa.Column("junior_artist_salary", sa.Integer(), nullable=False),
41
+ sa.Column(
42
+ "id",
43
+ sqlalchemy_utils.types.uuid.UUIDType(binary=False),
44
+ default=uuid.uuid4,
45
+ nullable=False,
46
+ ),
47
+ sa.Column("created_at", sa.DateTime(), nullable=True),
48
+ sa.Column("updated_at", sa.DateTime(), nullable=True),
49
+ sa.ForeignKeyConstraint(
50
+ ["department_id"],
51
+ ["department.id"],
52
+ ),
53
+ sa.PrimaryKeyConstraint("id"),
54
+ )
55
+ with op.batch_alter_table("salary_scale", schema=None) as batch_op:
56
+ batch_op.create_index(
57
+ batch_op.f("ix_salary_scale_department_id"),
58
+ ["department_id"],
59
+ unique=False,
60
+ )
61
+ # ### end Alembic commands ###
62
+
63
+
64
+ def downgrade():
65
+ # ### commands auto generated by Alembic - please adjust! ###
66
+ with op.batch_alter_table("salary_scale", schema=None) as batch_op:
67
+ batch_op.drop_index(batch_op.f("ix_salary_scale_department_id"))
68
+
69
+ op.drop_table("salary_scale")
70
+ # ### end Alembic commands ###
@@ -0,0 +1,68 @@
1
+ """Introduce Plugin table
2
+
3
+ Revision ID: 4aab1f84ad72
4
+ Revises: d25118cddcaa
5
+ Create Date: 2025-04-14 15:18:29.346896
6
+
7
+ """
8
+
9
+ from alembic import op
10
+ import sqlalchemy as sa
11
+ import sqlalchemy_utils
12
+ import sqlalchemy_utils
13
+ import uuid
14
+
15
+ # revision identifiers, used by Alembic.
16
+ revision = "4aab1f84ad72"
17
+ down_revision = "d25118cddcaa"
18
+ branch_labels = None
19
+ depends_on = None
20
+
21
+
22
+ def upgrade():
23
+ # ### commands auto generated by Alembic - please adjust! ###
24
+ op.create_table(
25
+ "plugin",
26
+ sa.Column("plugin_id", sa.String(length=80), nullable=False),
27
+ sa.Column("name", sa.String(length=80), nullable=False),
28
+ sa.Column("description", sa.Text(), nullable=True),
29
+ sa.Column("version", sa.String(length=50), nullable=False),
30
+ sa.Column("maintainer_name", sa.String(length=200), nullable=False),
31
+ sa.Column(
32
+ "maintainer_email",
33
+ sqlalchemy_utils.types.email.EmailType(length=255),
34
+ nullable=True,
35
+ ),
36
+ sa.Column(
37
+ "website", sqlalchemy_utils.types.url.URLType(), nullable=True
38
+ ),
39
+ sa.Column("license", sa.String(length=80), nullable=False),
40
+ sa.Column(
41
+ "id",
42
+ sqlalchemy_utils.types.uuid.UUIDType(binary=False),
43
+ default=uuid.uuid4,
44
+ nullable=False,
45
+ ),
46
+ sa.Column("created_at", sa.DateTime(), nullable=True),
47
+ sa.Column("updated_at", sa.DateTime(), nullable=True),
48
+ sa.PrimaryKeyConstraint("id"),
49
+ )
50
+ with op.batch_alter_table("plugin", schema=None) as batch_op:
51
+ batch_op.create_index(
52
+ batch_op.f("ix_plugin_name"), ["name"], unique=False
53
+ )
54
+ batch_op.create_index(
55
+ batch_op.f("ix_plugin_plugin_id"), ["plugin_id"], unique=True
56
+ )
57
+
58
+ # ### end Alembic commands ###
59
+
60
+
61
+ def downgrade():
62
+ # ### commands auto generated by Alembic - please adjust! ###
63
+ with op.batch_alter_table("plugin", schema=None) as batch_op:
64
+ batch_op.drop_index(batch_op.f("ix_plugin_plugin_id"))
65
+ batch_op.drop_index(batch_op.f("ix_plugin_name"))
66
+
67
+ op.drop_table("plugin")
68
+ # ### end Alembic commands ###
@@ -0,0 +1,33 @@
1
+ """add currency field to budgets
2
+
3
+ Revision ID: 7a16258f2fab
4
+ Revises: 8ab98c178903
5
+ Create Date: 2025-04-21 22:27:22.703525
6
+
7
+ """
8
+
9
+ from alembic import op
10
+ import sqlalchemy as sa
11
+
12
+
13
+ # revision identifiers, used by Alembic.
14
+ revision = "7a16258f2fab"
15
+ down_revision = "8ab98c178903"
16
+ branch_labels = None
17
+ depends_on = None
18
+
19
+
20
+ def upgrade():
21
+ # ### commands auto generated by Alembic - please adjust! ###
22
+ with op.batch_alter_table("budget", schema=None) as batch_op:
23
+ batch_op.add_column(sa.Column("currency", sa.String(length=3)))
24
+ # ### end Alembic commands ###
25
+
26
+
27
+ def downgrade():
28
+ # ### commands auto generated by Alembic - please adjust! ###
29
+ with op.batch_alter_table("task", schema=None) as batch_op:
30
+ batch_op.alter_column(
31
+ "difficulty", existing_type=sa.INTEGER(), nullable=True
32
+ )
33
+ # ### end Alembic commands ###
@@ -0,0 +1,57 @@
1
+ """add project bugdet table
2
+
3
+ Revision ID: 83e2f33a9b14
4
+ Revises: 45f739ef962a
5
+ Create Date: 2025-04-09 20:17:08.331320
6
+
7
+ """
8
+
9
+ from alembic import op
10
+ import sqlalchemy as sa
11
+ import sqlalchemy_utils
12
+ import sqlalchemy_utils
13
+ import uuid
14
+
15
+ # revision identifiers, used by Alembic.
16
+ revision = "83e2f33a9b14"
17
+ down_revision = "45f739ef962a"
18
+ branch_labels = None
19
+ depends_on = None
20
+
21
+
22
+ def upgrade():
23
+ # ### commands auto generated by Alembic - please adjust! ###
24
+ op.create_table(
25
+ "budget",
26
+ sa.Column(
27
+ "project_id",
28
+ sqlalchemy_utils.types.uuid.UUIDType(binary=False),
29
+ default=uuid.uuid4,
30
+ nullable=False,
31
+ ),
32
+ sa.Column("revision", sa.Integer(), nullable=False),
33
+ sa.Column("name", sa.String(length=255), nullable=False),
34
+ sa.Column(
35
+ "id",
36
+ sqlalchemy_utils.types.uuid.UUIDType(binary=False),
37
+ default=uuid.uuid4,
38
+ nullable=False,
39
+ ),
40
+ sa.Column("created_at", sa.DateTime(), nullable=True),
41
+ sa.Column("updated_at", sa.DateTime(), nullable=True),
42
+ sa.PrimaryKeyConstraint("id"),
43
+ )
44
+ with op.batch_alter_table("budget", schema=None) as batch_op:
45
+ batch_op.create_index(
46
+ batch_op.f("ix_budget_project_id"), ["project_id"], unique=False
47
+ )
48
+ # ### end Alembic commands ###
49
+
50
+
51
+ def downgrade():
52
+ # ### commands auto generated by Alembic - please adjust! ###
53
+ with op.batch_alter_table("budget", schema=None) as batch_op:
54
+ batch_op.drop_index(batch_op.f("ix_budget_project_id"))
55
+
56
+ op.drop_table("budget")
57
+ # ### end Alembic commands ###
@@ -0,0 +1,123 @@
1
+ """add budget entry table
2
+
3
+ Revision ID: 8ab98c178903
4
+ Revises: 83e2f33a9b14
5
+ Create Date: 2025-04-15 16:56:25.451489
6
+
7
+ """
8
+
9
+ from alembic import op
10
+ import sqlalchemy as sa
11
+ import sqlalchemy_utils
12
+ import sqlalchemy_utils
13
+ import uuid
14
+
15
+ # revision identifiers, used by Alembic.
16
+ revision = "8ab98c178903"
17
+ down_revision = "83e2f33a9b14"
18
+ branch_labels = None
19
+ depends_on = None
20
+
21
+ from zou.app.models.person import POSITION_TYPES, SENIORITY_TYPES
22
+
23
+
24
+ def upgrade():
25
+ # ### commands auto generated by Alembic - please adjust! ###
26
+ op.create_table(
27
+ "budget_entry",
28
+ sa.Column(
29
+ "budget_id",
30
+ sqlalchemy_utils.types.uuid.UUIDType(binary=False),
31
+ default=uuid.uuid4,
32
+ nullable=False,
33
+ ),
34
+ sa.Column(
35
+ "department_id",
36
+ sqlalchemy_utils.types.uuid.UUIDType(binary=False),
37
+ default=uuid.uuid4,
38
+ nullable=False,
39
+ ),
40
+ sa.Column(
41
+ "person_id",
42
+ sqlalchemy_utils.types.uuid.UUIDType(binary=False),
43
+ default=uuid.uuid4,
44
+ nullable=True,
45
+ ),
46
+ sa.Column("start_date", sa.Date(), nullable=False),
47
+ sa.Column("months_duration", sa.Integer(), nullable=False),
48
+ sa.Column("daily_salary", sa.Float(), nullable=False),
49
+ sa.Column(
50
+ "position",
51
+ sqlalchemy_utils.types.choice.ChoiceType(POSITION_TYPES),
52
+ nullable=True,
53
+ ),
54
+ sa.Column(
55
+ "seniority",
56
+ sqlalchemy_utils.types.choice.ChoiceType(SENIORITY_TYPES),
57
+ nullable=True,
58
+ ),
59
+ sa.Column(
60
+ "id",
61
+ sqlalchemy_utils.types.uuid.UUIDType(binary=False),
62
+ default=uuid.uuid4,
63
+ nullable=False,
64
+ ),
65
+ sa.Column("created_at", sa.DateTime(), nullable=True),
66
+ sa.Column("updated_at", sa.DateTime(), nullable=True),
67
+ sa.ForeignKeyConstraint(
68
+ ["budget_id"],
69
+ ["budget.id"],
70
+ ),
71
+ sa.ForeignKeyConstraint(
72
+ ["department_id"],
73
+ ["department.id"],
74
+ ),
75
+ sa.ForeignKeyConstraint(
76
+ ["person_id"],
77
+ ["person.id"],
78
+ ),
79
+ sa.PrimaryKeyConstraint("id"),
80
+ )
81
+ with op.batch_alter_table("budget_entry", schema=None) as batch_op:
82
+ batch_op.create_index(
83
+ batch_op.f("ix_budget_entry_budget_id"),
84
+ ["budget_id"],
85
+ unique=False,
86
+ )
87
+ batch_op.create_index(
88
+ batch_op.f("ix_budget_entry_department_id"),
89
+ ["department_id"],
90
+ unique=False,
91
+ )
92
+ batch_op.create_index(
93
+ batch_op.f("ix_budget_entry_person_id"),
94
+ ["person_id"],
95
+ unique=False,
96
+ )
97
+
98
+ with op.batch_alter_table("budget", schema=None) as batch_op:
99
+ batch_op.alter_column(
100
+ "project_id", existing_type=sa.UUID(), nullable=True
101
+ )
102
+ batch_op.create_foreign_key(
103
+ "budget_project_id_fkey", "project", ["project_id"], ["id"]
104
+ )
105
+
106
+ # ### end Alembic commands ###
107
+
108
+
109
+ def downgrade():
110
+ # ### commands auto generated by Alembic - please adjust! ###
111
+ with op.batch_alter_table("budget", schema=None) as batch_op:
112
+ batch_op.drop_constraint("budget_project_id_fkey", type_="foreignkey")
113
+ batch_op.alter_column(
114
+ "project_id", existing_type=sa.UUID(), nullable=False
115
+ )
116
+
117
+ with op.batch_alter_table("budget_entry", schema=None) as batch_op:
118
+ batch_op.drop_index(batch_op.f("ix_budget_entry_person_id"))
119
+ batch_op.drop_index(batch_op.f("ix_budget_entry_department_id"))
120
+ batch_op.drop_index(batch_op.f("ix_budget_entry_budget_id"))
121
+
122
+ op.drop_table("budget_entry")
123
+ # ### end Alembic commands ###